Implement OFPT_TABLE_STATUS Message.
[cascardo/ovs.git] / tests / test-ovn.c
index a6d8b80..ae2787c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015 Nicira, Inc.
+ * Copyright (c) 2015, 2016 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "dynamic-string.h"
 #include "fatal-signal.h"
 #include "match.h"
+#include "ofp-actions.h"
+#include "ofpbuf.h"
+#include "ovn/lib/actions.h"
 #include "ovn/lib/expr.h"
 #include "ovn/lib/lex.h"
 #include "ovs-thread.h"
 #include "ovstest.h"
 #include "shash.h"
+#include "simap.h"
 #include "util.h"
 #include "openvswitch/vlog.h"
 
 /* --relops: Bitmap of the relational operators to test, in exhaustive test. */
 static unsigned int test_relops;
 
-/* --vars: Number of variables to test, in exhaustive test. */
-static int test_vars = 2;
+/* --nvars: Number of numeric variables to test, in exhaustive test. */
+static int test_nvars = 2;
+
+/* --svars: Number of string variables to test, in exhaustive test. */
+static int test_svars = 2;
 
 /* --bits: Number of bits per variable, in exhaustive test. */
 static int test_bits = 3;
@@ -95,7 +102,7 @@ test_lex(struct ovs_cmdl_context *ctx OVS_UNUSED)
 
     ds_init(&input);
     ds_init(&output);
-    while (!ds_get_line(&input, stdin)) {
+    while (!ds_get_test_line(&input, stdin)) {
         struct lexer lexer;
 
         lexer_init(&lexer, ds_cstr(&input));
@@ -131,13 +138,15 @@ create_symtab(struct shash *symtab)
 {
     shash_init(symtab);
 
-    expr_symtab_add_string(symtab, "inport", NULL);
-    expr_symtab_add_string(symtab, "outport", NULL);
+    /* Reserve a pair of registers for the logical inport and outport.  A full
+     * 32-bit register each is bigger than we need, but the expression code
+     * doesn't yet support string fields that occupy less than a full OXM. */
+    expr_symtab_add_string(symtab, "inport", MFF_REG6, NULL);
+    expr_symtab_add_string(symtab, "outport", MFF_REG7, NULL);
 
     expr_symtab_add_field(symtab, "xreg0", MFF_XREG0, NULL, false);
     expr_symtab_add_field(symtab, "xreg1", MFF_XREG1, NULL, false);
     expr_symtab_add_field(symtab, "xreg2", MFF_XREG2, NULL, false);
-    expr_symtab_add_field(symtab, "xreg3", MFF_XREG3, NULL, false);
 
     expr_symtab_add_subfield(symtab, "reg0", NULL, "xreg0[32..63]");
     expr_symtab_add_subfield(symtab, "reg1", NULL, "xreg0[0..31]");
@@ -145,8 +154,6 @@ create_symtab(struct shash *symtab)
     expr_symtab_add_subfield(symtab, "reg3", NULL, "xreg1[0..31]");
     expr_symtab_add_subfield(symtab, "reg4", NULL, "xreg2[32..63]");
     expr_symtab_add_subfield(symtab, "reg5", NULL, "xreg2[0..31]");
-    expr_symtab_add_subfield(symtab, "reg6", NULL, "xreg3[32..63]");
-    expr_symtab_add_subfield(symtab, "reg7", NULL, "xreg3[0..31]");
 
     expr_symtab_add_field(symtab, "eth.src", MFF_ETH_SRC, NULL, false);
     expr_symtab_add_field(symtab, "eth.dst", MFF_ETH_DST, NULL, false);
@@ -228,15 +235,23 @@ create_symtab(struct shash *symtab)
                           "mutual_recurse_2 != 0", false);
     expr_symtab_add_field(symtab, "mutual_recurse_2", MFF_XREG0,
                           "mutual_recurse_1 != 0", false);
+    expr_symtab_add_string(symtab, "big_string", MFF_XREG0, NULL);
 }
 
 static void
 test_parse_expr__(int steps)
 {
     struct shash symtab;
+    struct simap ports;
     struct ds input;
 
     create_symtab(&symtab);
+
+    simap_init(&ports);
+    simap_put(&ports, "eth0", 5);
+    simap_put(&ports, "eth1", 6);
+    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
+
     ds_init(&input);
     while (!ds_get_test_line(&input, stdin)) {
         struct expr *expr;
@@ -256,10 +271,18 @@ test_parse_expr__(int steps)
             }
         }
         if (!error) {
-            struct ds output = DS_EMPTY_INITIALIZER;
-            expr_format(expr, &output);
-            puts(ds_cstr(&output));
-            ds_destroy(&output);
+            if (steps > 3) {
+                struct hmap matches;
+
+                expr_to_matches(expr, &ports, &matches);
+                expr_matches_print(&matches, stdout);
+                expr_matches_destroy(&matches);
+            } else {
+                struct ds output = DS_EMPTY_INITIALIZER;
+                expr_format(expr, &output);
+                puts(ds_cstr(&output));
+                ds_destroy(&output);
+            }
         } else {
             puts(error);
             free(error);
@@ -268,6 +291,7 @@ test_parse_expr__(int steps)
     }
     ds_destroy(&input);
 
+    simap_destroy(&ports);
     expr_symtab_destroy(&symtab);
     shash_destroy(&symtab);
 }
@@ -295,6 +319,12 @@ test_normalize_expr(struct ovs_cmdl_context *ctx OVS_UNUSED)
 {
     test_parse_expr__(3);
 }
+
+static void
+test_expr_to_flows(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+    test_parse_expr__(4);
+}
 \f
 /* Evaluate an expression. */
 
@@ -317,37 +347,45 @@ evaluate_andor_expr(const struct expr *expr, unsigned int subst, int n_bits,
 static bool
 evaluate_cmp_expr(const struct expr *expr, unsigned int subst, int n_bits)
 {
-    int var_idx = expr->cmp.symbol->name[0] - 'a';
-    unsigned var_mask = (1u << n_bits) - 1;
-    unsigned int arg1 = (subst >> (var_idx * n_bits)) & var_mask;
-    unsigned int arg2 = ntohll(expr->cmp.value.integer);
-    unsigned int mask = ntohll(expr->cmp.mask.integer);
-
-    ovs_assert(!(mask & ~var_mask));
-    ovs_assert(!(arg2 & ~var_mask));
-    ovs_assert(!(arg2 & ~mask));
-
-    arg1 &= mask;
-    switch (expr->cmp.relop) {
-    case EXPR_R_EQ:
-        return arg1 == arg2;
+    int var_idx = atoi(expr->cmp.symbol->name + 1);
+    if (expr->cmp.symbol->name[0] == 'n') {
+        unsigned var_mask = (1u << n_bits) - 1;
+        unsigned int arg1 = (subst >> (var_idx * n_bits)) & var_mask;
+        unsigned int arg2 = ntohll(expr->cmp.value.integer);
+        unsigned int mask = ntohll(expr->cmp.mask.integer);
 
-    case EXPR_R_NE:
-        return arg1 != arg2;
+        ovs_assert(!(mask & ~var_mask));
+        ovs_assert(!(arg2 & ~var_mask));
+        ovs_assert(!(arg2 & ~mask));
 
-    case EXPR_R_LT:
-        return arg1 < arg2;
+        arg1 &= mask;
+        switch (expr->cmp.relop) {
+        case EXPR_R_EQ:
+            return arg1 == arg2;
 
-    case EXPR_R_LE:
-        return arg1 <= arg2;
+        case EXPR_R_NE:
+            return arg1 != arg2;
 
-    case EXPR_R_GT:
-        return arg1 > arg2;
+        case EXPR_R_LT:
+            return arg1 < arg2;
 
-    case EXPR_R_GE:
-        return arg1 >= arg2;
+        case EXPR_R_LE:
+            return arg1 <= arg2;
 
-    default:
+        case EXPR_R_GT:
+            return arg1 > arg2;
+
+        case EXPR_R_GE:
+            return arg1 >= arg2;
+
+        default:
+            OVS_NOT_REACHED();
+        }
+    } else if (expr->cmp.symbol->name[0] == 's') {
+        unsigned int arg1 = (subst >> (test_nvars * n_bits + var_idx)) & 1;
+        unsigned int arg2 = atoi(expr->cmp.string);
+        return arg1 == arg2;
+    } else {
         OVS_NOT_REACHED();
     }
 }
@@ -652,15 +690,31 @@ test_tree_shape(struct ovs_cmdl_context *ctx)
 /* Sets 'expr' to the first possible terminal expression.  'var' should be the
  * first variable in the ones to be tested. */
 static void
-init_terminal(struct expr *expr, const struct expr_symbol *var)
+init_terminal(struct expr *expr, int phase,
+              const struct expr_symbol *nvars[], int n_nvars,
+              const struct expr_symbol *svars[], int n_svars)
 {
-    expr->type = EXPR_T_CMP;
-    expr->cmp.symbol = var;
-    expr->cmp.relop = rightmost_1bit_idx(test_relops);
-    memset(&expr->cmp.value, 0, sizeof expr->cmp.value);
-    memset(&expr->cmp.mask, 0, sizeof expr->cmp.mask);
-    expr->cmp.value.integer = htonll(0);
-    expr->cmp.mask.integer = htonll(1);
+    if (phase < 1 && n_nvars) {
+        expr->type = EXPR_T_CMP;
+        expr->cmp.symbol = nvars[0];
+        expr->cmp.relop = rightmost_1bit_idx(test_relops);
+        memset(&expr->cmp.value, 0, sizeof expr->cmp.value);
+        memset(&expr->cmp.mask, 0, sizeof expr->cmp.mask);
+        expr->cmp.value.integer = htonll(0);
+        expr->cmp.mask.integer = htonll(1);
+        return;
+    }
+
+    if (phase < 2 && n_svars) {
+        expr->type = EXPR_T_CMP;
+        expr->cmp.symbol = svars[0];
+        expr->cmp.relop = EXPR_R_EQ;
+        expr->cmp.string = xstrdup("0");
+        return;
+    }
+
+    expr->type = EXPR_T_BOOLEAN;
+    expr->boolean = false;
 }
 
 /* Returns 'x' with the rightmost contiguous string of 1s changed to 0s,
@@ -696,8 +750,9 @@ next_relop(enum expr_relop relop)
 /* Advances 'expr' to the next possible terminal expression within the 'n_vars'
  * variables of 'n_bits' bits each in 'vars[]'. */
 static bool
-next_terminal(struct expr *expr, const struct expr_symbol *vars[], int n_vars,
-              int n_bits)
+next_terminal(struct expr *expr,
+              const struct expr_symbol *nvars[], int n_nvars, int n_bits,
+              const struct expr_symbol *svars[], int n_svars)
 {
     if (expr->type == EXPR_T_BOOLEAN) {
         if (expr->boolean) {
@@ -708,6 +763,21 @@ next_terminal(struct expr *expr, const struct expr_symbol *vars[], int n_vars,
         }
     }
 
+    if (!expr->cmp.symbol->width) {
+        int next_value = atoi(expr->cmp.string) + 1;
+        free(expr->cmp.string);
+        if (next_value > 1) {
+            expr->cmp.symbol = next_var(expr->cmp.symbol, svars, n_svars);
+            if (!expr->cmp.symbol) {
+                init_terminal(expr, 2, nvars, n_nvars, svars, n_svars);
+                return true;
+            }
+            next_value = 0;
+        }
+        expr->cmp.string = xasprintf("%d", next_value);
+        return true;
+    }
+
     unsigned int next;
 
     next = (ntohll(expr->cmp.value.integer)
@@ -720,10 +790,9 @@ next_terminal(struct expr *expr, const struct expr_symbol *vars[], int n_vars,
             enum expr_relop old_relop = expr->cmp.relop;
             expr->cmp.relop = next_relop(old_relop);
             if (expr->cmp.relop <= old_relop) {
-                expr->cmp.symbol = next_var(expr->cmp.symbol,vars, n_vars);
+                expr->cmp.symbol = next_var(expr->cmp.symbol, nvars, n_nvars);
                 if (!expr->cmp.symbol) {
-                    expr->type = EXPR_T_BOOLEAN;
-                    expr->boolean = false;
+                    init_terminal(expr, 1, nvars, n_nvars, svars, n_svars);
                     return true;
                 }
             }
@@ -802,14 +871,19 @@ free_rule(struct test_rule *test_rule)
 static int
 test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
                              struct expr *terminals[], int n_terminals,
-                             const struct expr_symbol *vars[], int n_vars,
-                             int n_bits)
+                             const struct expr_symbol *nvars[], int n_nvars,
+                             int n_bits,
+                             const struct expr_symbol *svars[], int n_svars)
 {
+    struct simap string_map = SIMAP_INITIALIZER(&string_map);
+    simap_put(&string_map, "0", 0);
+    simap_put(&string_map, "1", 1);
+
     int n_tested = 0;
 
     const unsigned int var_mask = (1u << n_bits) - 1;
     for (int i = 0; i < n_terminals; i++) {
-        init_terminal(terminals[i], vars[0]);
+        init_terminal(terminals[i], 0, nvars, n_nvars, svars, n_svars);
     }
 
     struct ds s = DS_EMPTY_INITIALIZER;
@@ -819,12 +893,14 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
         for (int i = n_terminals - 1; ; i--) {
             if (!i) {
                 ds_destroy(&s);
+                simap_destroy(&string_map);
                 return n_tested;
             }
-            if (next_terminal(terminals[i], vars, n_vars, n_bits)) {
+            if (next_terminal(terminals[i], nvars, n_nvars, n_bits,
+                              svars, n_svars)) {
                 break;
             }
-            init_terminal(terminals[i], vars[0]);
+            init_terminal(terminals[i], 0, nvars, n_nvars, svars, n_svars);
         }
         ovs_assert(expr_honors_invariants(expr));
 
@@ -857,27 +933,19 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
         if (operation >= OP_FLOW) {
             struct expr_match *m;
             struct test_rule *test_rule;
-            uint32_t n_conjs;
 
-            n_conjs = expr_to_matches(modified, &matches);
+            expr_to_matches(modified, &string_map, &matches);
 
             classifier_init(&cls, NULL);
             HMAP_FOR_EACH (m, hmap_node, &matches) {
                 test_rule = xmalloc(sizeof *test_rule);
                 cls_rule_init(&test_rule->cr, &m->match, 0);
-                classifier_insert(&cls, &test_rule->cr, m->conjunctions, m->n);
-            }
-            for (uint32_t conj_id = 1; conj_id <= n_conjs; conj_id++) {
-                struct match match;
-                match_init_catchall(&match);
-                match_set_conj_id(&match, conj_id);
-
-                test_rule = xmalloc(sizeof *test_rule);
-                cls_rule_init(&test_rule->cr, &match, 0);
-                classifier_insert(&cls, &test_rule->cr, NULL, 0);
+                classifier_insert(&cls, &test_rule->cr, CLS_MIN_VERSION,
+                                  m->conjunctions, m->n);
             }
         }
-        for (int subst = 0; subst < 1 << (n_bits * n_vars); subst++) {
+        for (int subst = 0; subst < 1 << (n_bits * n_nvars + n_svars);
+             subst++) {
             bool expected = evaluate_expr(expr, subst, n_bits);
             bool actual = evaluate_expr(modified, subst, n_bits);
             if (actual != expected) {
@@ -893,22 +961,31 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
                         "%s evaluates to %d, but %s evaluates to %d, for",
                         ds_cstr(&expr_s), expected,
                         ds_cstr(&modified_s), actual);
-                for (int i = 0; i < n_vars; i++) {
+                for (int i = 0; i < n_nvars; i++) {
                     if (i > 0) {
                         fputs(",", stderr);
                     }
-                    fprintf(stderr, " %c = 0x%x", 'a' + i,
+                    fprintf(stderr, " n%d = 0x%x", i,
                             (subst >> (n_bits * i)) & var_mask);
                 }
+                for (int i = 0; i < n_svars; i++) {
+                    fprintf(stderr, ", s%d = \"%d\"", i,
+                            (subst >> (n_bits * n_nvars + i)) & 1);
+                }
                 putc('\n', stderr);
                 exit(EXIT_FAILURE);
             }
 
             if (operation >= OP_FLOW) {
-                for (int i = 0; i < n_vars; i++) {
+                for (int i = 0; i < n_nvars; i++) {
                     f.regs[i] = (subst >> (i * n_bits)) & var_mask;
                 }
-                bool found = classifier_lookup(&cls, &f, NULL) != NULL;
+                for (int i = 0; i < n_svars; i++) {
+                    f.regs[n_nvars + i] = ((subst >> (n_nvars * n_bits + i))
+                                           & 1);
+                }
+                bool found = classifier_lookup(&cls, CLS_MIN_VERSION,
+                                               &f, NULL) != NULL;
                 if (expected != found) {
                     struct ds expr_s, modified_s;
 
@@ -921,35 +998,21 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
                     fprintf(stderr,
                             "%s and %s evaluate to %d, for",
                             ds_cstr(&expr_s), ds_cstr(&modified_s), expected);
-                    for (int i = 0; i < n_vars; i++) {
+                    for (int i = 0; i < n_nvars; i++) {
                         if (i > 0) {
                             fputs(",", stderr);
                         }
-                        fprintf(stderr, " %c = 0x%x", 'a' + i,
+                        fprintf(stderr, " n%d = 0x%x", i,
                                 (subst >> (n_bits * i)) & var_mask);
                     }
+                    for (int i = 0; i < n_svars; i++) {
+                        fprintf(stderr, ", s%d = \"%d\"", i,
+                                (subst >> (n_bits * n_nvars + i)) & 1);
+                    }
                     fputs(".\n", stderr);
 
                     fprintf(stderr, "Converted to classifier:\n");
-
-                    struct expr_match *m;
-                    HMAP_FOR_EACH (m, hmap_node, &matches) {
-                        char *s = match_to_string(&m->match,
-                                                  OFP_DEFAULT_PRIORITY);
-                        fputs(s, stderr);
-                        if (m->n) {
-                            for (int i = 0; i < m->n; i++) {
-                                const struct cls_conjunction *c
-                                    = &m->conjunctions[i];
-                                fprintf(stderr,
-                                        "%c conjunction(%"PRIu32", %d/%d)",
-                                        i == 0 ? ':' : ',',
-                                        c->id, c->clause, c->n_clauses);
-                            }
-                        }
-                        putc('\n', stderr);
-                    }
-
+                    expr_matches_print(&matches, stderr);
                     fprintf(stderr,
                             "However, %s flow was found in the classifier.\n",
                             found ? "a" : "no");
@@ -958,7 +1021,6 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
             }
         }
         if (operation >= OP_FLOW) {
-            struct expr_match *m, *n;
             struct test_rule *test_rule;
 
             CLS_FOR_EACH (test_rule, cr, &cls) {
@@ -968,12 +1030,7 @@ test_tree_shape_exhaustively(struct expr *expr, struct shash *symtab,
             classifier_destroy(&cls);
             ovsrcu_quiesce();
 
-            HMAP_FOR_EACH_SAFE (m, n, hmap_node, &matches) {
-                hmap_remove(&matches, &m->hmap_node);
-                free(m->conjunctions);
-                free(m);
-            }
-            hmap_destroy(&matches);
+            expr_matches_destroy(&matches);
         }
         expr_destroy(modified);
     }
@@ -1018,16 +1075,25 @@ test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
     int n_tses;
 
     struct shash symtab;
-    const struct expr_symbol *vars[4];
+    const struct expr_symbol *nvars[4];
+    const struct expr_symbol *svars[4];
 
-    ovs_assert(test_vars <= ARRAY_SIZE(vars));
+    ovs_assert(test_nvars <= ARRAY_SIZE(nvars));
+    ovs_assert(test_svars <= ARRAY_SIZE(svars));
+    ovs_assert(test_nvars + test_svars <= FLOW_N_REGS);
 
     shash_init(&symtab);
-    for (int i = 0; i < test_vars; i++) {
-        char name[2] = { 'a' + i, '\0' };
-
-        vars[i] = expr_symtab_add_field(&symtab, name, MFF_REG0 + i, NULL,
-                                       false);
+    for (int i = 0; i < test_nvars; i++) {
+        char *name = xasprintf("n%d", i);
+        nvars[i] = expr_symtab_add_field(&symtab, name, MFF_REG0 + i, NULL,
+                                         false);
+        free(name);
+    }
+    for (int i = 0; i < test_svars; i++) {
+        char *name = xasprintf("s%d", i);
+        svars[i] = expr_symtab_add_string(&symtab, name,
+                                          MFF_REG0 + test_nvars + i, NULL);
+        free(name);
     }
 
 #ifndef _WIN32
@@ -1062,7 +1128,8 @@ test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
                 if (!pid) {
                     test_tree_shape_exhaustively(expr, &symtab,
                                                  terminals, n_terminals,
-                                                 vars, test_vars, test_bits);
+                                                 nvars, test_nvars, test_bits,
+                                                 svars, test_svars);
                     expr_destroy(expr);
                     exit(0);
                 } else {
@@ -1076,7 +1143,8 @@ test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
             {
                 n_tested += test_tree_shape_exhaustively(
                     expr, &symtab, terminals, n_terminals,
-                    vars, test_vars, test_bits);
+                    nvars, test_nvars, test_bits,
+                    svars, test_svars);
             }
             expr_destroy(expr);
         }
@@ -1108,12 +1176,25 @@ test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
     } else {
         printf(" all %d-terminal expressions", n_terminals);
     }
-    printf(" with %d vars each of %d bits in terms of operators",
-           test_vars, test_bits);
-    for (unsigned int relops = test_relops; relops;
-         relops = zero_rightmost_1bit(relops)) {
-        enum expr_relop r = rightmost_1bit_idx(relops);
-        printf(" %s", expr_relop_to_string(r));
+    if (test_nvars || test_svars) {
+        printf(" with");
+        if (test_nvars) {
+            printf(" %d numeric vars (each %d bits) in terms of operators",
+                   test_nvars, test_bits);
+            for (unsigned int relops = test_relops; relops;
+                 relops = zero_rightmost_1bit(relops)) {
+                enum expr_relop r = rightmost_1bit_idx(relops);
+                printf(" %s", expr_relop_to_string(r));
+            }
+        }
+        if (test_nvars && test_svars) {
+            printf (" and");
+        }
+        if (test_svars) {
+            printf(" %d string vars", test_svars);
+        }
+    } else {
+        printf(" in terms of Boolean constants only");
     }
     printf(".\n");
 
@@ -1121,6 +1202,72 @@ test_exhaustive(struct ovs_cmdl_context *ctx OVS_UNUSED)
     shash_destroy(&symtab);
 }
 \f
+/* Actions. */
+
+static void
+test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
+{
+    struct shash symtab;
+    struct simap ports, ct_zones;
+    struct ds input;
+
+    create_symtab(&symtab);
+
+    simap_init(&ports);
+    simap_put(&ports, "eth0", 5);
+    simap_put(&ports, "eth1", 6);
+    simap_put(&ports, "LOCAL", ofp_to_u16(OFPP_LOCAL));
+    simap_init(&ct_zones);
+
+    ds_init(&input);
+    while (!ds_get_test_line(&input, stdin)) {
+        struct ofpbuf ofpacts;
+        struct expr *prereqs;
+        char *error;
+
+        ofpbuf_init(&ofpacts, 0);
+
+        struct action_params ap = {
+            .symtab = &symtab,
+            .ports = &ports,
+            .ct_zones = &ct_zones,
+
+            .n_tables = 16,
+            .first_ptable = 16,
+            .cur_ltable = 10,
+            .output_ptable = 64,
+        };
+        error = actions_parse_string(ds_cstr(&input), &ap, &ofpacts, &prereqs);
+        if (!error) {
+            struct ds output;
+
+            ds_init(&output);
+            ds_put_cstr(&output, "actions=");
+            ofpacts_format(ofpacts.data, ofpacts.size, &output);
+            ds_put_cstr(&output, ", prereqs=");
+            if (prereqs) {
+                expr_format(prereqs, &output);
+            } else {
+                ds_put_char(&output, '1');
+            }
+            puts(ds_cstr(&output));
+            ds_destroy(&output);
+        } else {
+            puts(error);
+            free(error);
+        }
+
+        expr_destroy(prereqs);
+        ofpbuf_uninit(&ofpacts);
+    }
+    ds_destroy(&input);
+
+    simap_destroy(&ports);
+    simap_destroy(&ct_zones);
+    expr_symtab_destroy(&symtab);
+    shash_destroy(&symtab);
+}
+\f
 static unsigned int
 parse_relops(const char *s)
 {
@@ -1160,6 +1307,7 @@ parse-expr\n\
 annotate-expr\n\
 simplify-expr\n\
 normalize-expr\n\
+expr-to-flows\n\
   Parses OVN expressions from stdin and print them back on stdout after\n\
   differing degrees of analysis.  Available fields are based on packet\n\
   headers.\n\
@@ -1178,15 +1326,23 @@ tree-shape N\n\
 exhaustive N\n\
   Tests that all possible Boolean expressions with N terminals are properly\n\
   simplified, normalized, and converted to flows.  Available options:\n\
-    --relops=OPERATORS   Test only the specified Boolean operators.\n\
-                         OPERATORS may include == != < <= > >=, space or\n\
-                         comma separated.  Default is all operators.\n\
-    --vars=N  Number of variables to test, in range 1...4, default 2.\n\
-    --bits=N  Number of bits per variable, in range 1...3, default 3.\n\
+   Overall options:\n\
     --operation=OPERATION  Operation to test, one of: convert, simplify,\n\
         normalize, flow.  Default: flow.  'normalize' includes 'simplify',\n\
-        'flow' includes 'simplify' and 'normaize'.\n\
+        'flow' includes 'simplify' and 'normalize'.\n\
     --parallel=N  Number of processes to use in parallel, default 1.\n\
+   Numeric vars:\n\
+    --nvars=N  Number of numeric vars to test, in range 0...4, default 2.\n\
+    --bits=N  Number of bits per variable, in range 1...3, default 3.\n\
+    --relops=OPERATORS   Test only the specified Boolean operators.\n\
+                         OPERATORS may include == != < <= > >=, space or\n\
+                         comma separated.  Default is all operators.\n\
+   String vars:\n\
+    --svars=N  Number of string vars to test, in range 0...4, default 2.\n\
+\n\
+parse-actions\n\
+  Parses OVN actions from stdin and prints the equivalent OpenFlow actions\n\
+  on stdout.\n\
 ",
            program_name, program_name);
     exit(EXIT_SUCCESS);
@@ -1195,30 +1351,34 @@ exhaustive N\n\
 static void
 test_ovn_main(int argc, char *argv[])
 {
+    enum {
+        OPT_RELOPS = UCHAR_MAX + 1,
+        OPT_NVARS,
+        OPT_SVARS,
+        OPT_BITS,
+        OPT_OPERATION,
+        OPT_PARALLEL
+    };
+    static const struct option long_options[] = {
+        {"relops", required_argument, NULL, OPT_RELOPS},
+        {"nvars", required_argument, NULL, OPT_NVARS},
+        {"svars", required_argument, NULL, OPT_SVARS},
+        {"bits", required_argument, NULL, OPT_BITS},
+        {"operation", required_argument, NULL, OPT_OPERATION},
+        {"parallel", required_argument, NULL, OPT_PARALLEL},
+        {"more", no_argument, NULL, 'm'},
+        {"help", no_argument, NULL, 'h'},
+        {NULL, 0, NULL, 0},
+    };
+    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
+
     set_program_name(argv[0]);
 
     test_relops = parse_relops("== != < <= > >=");
     for (;;) {
-        enum {
-            OPT_RELOPS = UCHAR_MAX + 1,
-            OPT_VARS,
-            OPT_BITS,
-            OPT_OPERATION,
-            OPT_PARALLEL
-        };
-
-        static const struct option options[] = {
-            {"relops", required_argument, NULL, OPT_RELOPS},
-            {"vars", required_argument, NULL, OPT_VARS},
-            {"bits", required_argument, NULL, OPT_BITS},
-            {"operation", required_argument, NULL, OPT_OPERATION},
-            {"parallel", required_argument, NULL, OPT_PARALLEL},
-            {"more", no_argument, NULL, 'm'},
-            {"help", no_argument, NULL, 'h'},
-            {NULL, 0, NULL, 0},
-        };
         int option_index = 0;
-        int c = getopt_long (argc, argv, "", options, &option_index);
+        int c = getopt_long (argc, argv, short_options, long_options,
+                             &option_index);
 
         if (c == -1) {
             break;
@@ -1228,10 +1388,19 @@ test_ovn_main(int argc, char *argv[])
             test_relops = parse_relops(optarg);
             break;
 
-        case OPT_VARS:
-            test_vars = atoi(optarg);
-            if (test_vars < 1 || test_vars > 4) {
-                ovs_fatal(0, "number of variables must be between 1 and 4");
+        case OPT_NVARS:
+            test_nvars = atoi(optarg);
+            if (test_nvars < 0 || test_nvars > 4) {
+                ovs_fatal(0, "number of numeric variables must be "
+                          "between 0 and 4");
+            }
+            break;
+
+        case OPT_SVARS:
+            test_svars = atoi(optarg);
+            if (test_svars < 0 || test_svars > 4) {
+                ovs_fatal(0, "number of string variables must be "
+                          "between 0 and 4");
             }
             break;
 
@@ -1274,17 +1443,26 @@ test_ovn_main(int argc, char *argv[])
             abort();
         }
     }
+    free(short_options);
 
     static const struct ovs_cmdl_command commands[] = {
+        /* Lexer. */
         {"lex", NULL, 0, 0, test_lex},
+
+        /* Expressions. */
         {"parse-expr", NULL, 0, 0, test_parse_expr},
         {"annotate-expr", NULL, 0, 0, test_annotate_expr},
         {"simplify-expr", NULL, 0, 0, test_simplify_expr},
         {"normalize-expr", NULL, 0, 0, test_normalize_expr},
+        {"expr-to-flows", NULL, 0, 0, test_expr_to_flows},
         {"evaluate-expr", NULL, 1, 1, test_evaluate_expr},
         {"composition", NULL, 1, 1, test_composition},
         {"tree-shape", NULL, 1, 1, test_tree_shape},
         {"exhaustive", NULL, 1, 1, test_exhaustive},
+
+        /* Actions. */
+        {"parse-actions", NULL, 0, 0, test_parse_actions},
+
         {NULL, NULL, 0, 0, NULL},
     };
     struct ovs_cmdl_context ctx;