[PATCH v1 4/5] test: add flow parser unit tests

Stephen Hemminger stephen at networkplumber.org
Tue Jan 6 17:37:09 CET 2026


On Mon,  5 Jan 2026 22:30:22 +0100
Lukas Sismis <sismis at dyna-nic.com> wrote:

> Add comprehensive unit tests for the flow parser library.
> 
> Tests cover:
> - flow_parser_autotest: Tests rte_flow_parser_parse() with various
>   flow command strings including create, destroy, validate, query,
>   list, and isolate commands. Verifies parsed patterns, actions,
>   and attributes match expected values.
> 
> - flow_parser_helpers_autotest: Tests the standalone helper functions
>   parse_attr_str(), parse_pattern_str(), and parse_actions_str().
>   Verifies parsing of attributes, patterns with various item types,
>   and action sequences.
> 
> Run with: dpdk-test flow_parser_autotest
>           dpdk-test flow_parser_helpers_autotest
> 
> Signed-off-by: Lukas Sismis <sismis at dyna-nic.com>
> ---
>  app/test/meson.build        |   1 +
>  app/test/test_flow_parser.c | 226 ++++++++++++++++++++++++++++++++++++
>  2 files changed, 227 insertions(+)
>  create mode 100644 app/test/test_flow_parser.c
> 
> diff --git a/app/test/meson.build b/app/test/meson.build
> index efec42a6bf..03cf8aa70c 100644
> --- a/app/test/meson.build
> +++ b/app/test/meson.build
> @@ -88,6 +88,7 @@ source_file_deps = {
>      'test_fib6_perf.c': ['fib'],
>      'test_fib_perf.c': ['net', 'fib'],
>      'test_flow_classify.c': ['net', 'acl', 'table', 'ethdev', 'flow_classify'],
> +    'test_flow_parser.c': ['flow_parser', 'cmdline', 'ethdev'],
>      'test_func_reentrancy.c': ['hash', 'lpm'],
>      'test_graph.c': ['graph'],
>      'test_graph_feature_arc.c': ['graph'],
> diff --git a/app/test/test_flow_parser.c b/app/test/test_flow_parser.c
> new file mode 100644
> index 0000000000..1e65762d9f
> --- /dev/null
> +++ b/app/test/test_flow_parser.c
> @@ -0,0 +1,226 @@
> +/* SPDX-License-Identifier: BSD-3-Clause */
> +
> +#include <stdint.h>
> +#include <string.h>
> +
> +#include <rte_flow.h>
> +#include <rte_flow_parser.h>
> +
> +#include "test.h"
> +
> +static int
> +test_flow_parser_command_mapping(void)
> +{
> +	static const char *create_cmd =
> +		"flow create 0 ingress pattern eth / end "
> +		"actions drop / end";
> +	static const char *list_cmd = "flow list 0";
> +	uint8_t outbuf[4096];
> +	struct rte_flow_parser_output *out = (void *)outbuf;
> +	int ret;
> +
> +	ret = rte_flow_parser_init(NULL);
> +	if (ret != 0)
> +		return TEST_FAILED;
> +
> +	/* Test flow create command parsing */
> +	memset(outbuf, 0, sizeof(outbuf));
> +	ret = rte_flow_parser_parse(create_cmd, out, sizeof(outbuf));
> +	if (ret != 0) {
> +		printf("flow create parse failed: %d\n", ret);
> +		return TEST_FAILED;
> +	}
> +	if (out->command != RTE_FLOW_PARSER_CMD_CREATE) {
> +		printf("expected CREATE command, got %d\n", out->command);
> +		return TEST_FAILED;
> +	}
> +	if (out->port != 0) {
> +		printf("expected port 0, got %u\n", out->port);
> +		return TEST_FAILED;
> +	}
> +	/* pattern: eth / end = 2 items */
> +	if (out->args.vc.pattern_n != 2) {
> +		printf("expected 2 pattern items, got %u\n",
> +		       out->args.vc.pattern_n);
> +		return TEST_FAILED;
> +	}
> +	if (out->args.vc.pattern[0].type != RTE_FLOW_ITEM_TYPE_ETH) {
> +		printf("expected ETH pattern, got %d\n",
> +		       out->args.vc.pattern[0].type);
> +		return TEST_FAILED;
> +	}
> +	if (out->args.vc.pattern[1].type != RTE_FLOW_ITEM_TYPE_END) {
> +		printf("expected END pattern, got %d\n",
> +		       out->args.vc.pattern[1].type);
> +		return TEST_FAILED;
> +	}
> +	/* actions: drop / end = 2 items */
> +	if (out->args.vc.actions_n != 2) {
> +		printf("expected 2 action items, got %u\n",
> +		       out->args.vc.actions_n);
> +		return TEST_FAILED;
> +	}
> +	if (out->args.vc.actions[0].type != RTE_FLOW_ACTION_TYPE_DROP) {
> +		printf("expected DROP action, got %d\n",
> +		       out->args.vc.actions[0].type);
> +		return TEST_FAILED;
> +	}
> +	if (out->args.vc.actions[1].type != RTE_FLOW_ACTION_TYPE_END) {
> +		printf("expected END action, got %d\n",
> +		       out->args.vc.actions[1].type);
> +		return TEST_FAILED;
> +	}
> +	/* ingress attribute */
> +	if (out->args.vc.attr.ingress != 1 || out->args.vc.attr.egress != 0) {
> +		printf("expected ingress=1 egress=0\n");
> +		return TEST_FAILED;
> +	}
> +
> +	/* Test flow list command parsing */
> +	memset(outbuf, 0, sizeof(outbuf));
> +	ret = rte_flow_parser_parse(list_cmd, out, sizeof(outbuf));
> +	if (ret != 0 ||
> +	    out->command != RTE_FLOW_PARSER_CMD_LIST ||
> +	    out->port != 0)
> +		return TEST_FAILED;
> +
> +	return TEST_SUCCESS;
> +}
> +
> +static int
> +test_flow_parser_lightweight_helpers(void)
> +{
> +	const struct rte_flow_item *pattern = NULL;
> +	const struct rte_flow_action *actions = NULL;
> +	const struct rte_flow_action_queue *queue_conf;
> +	const struct rte_flow_action_mark *mark_conf;
> +	struct rte_flow_attr attr;
> +	uint32_t pattern_n = 0;
> +	uint32_t actions_n = 0;
> +	int ret;
> +
> +	ret = rte_flow_parser_init(NULL);
> +	if (ret != 0)
> +		return TEST_FAILED;
> +
> +	/* Test attribute parsing */
> +	memset(&attr, 0, sizeof(attr));
> +	ret = rte_flow_parser_parse_attr_str("ingress group 1 priority 5", &attr);
> +	if (ret != 0) {
> +		printf("attr parse failed: %d\n", ret);
> +		return TEST_FAILED;
> +	}
> +	if (attr.group != 1 || attr.priority != 5 ||
> +	    attr.ingress != 1 || attr.egress != 0) {
> +		printf("attr mismatch: group=%u priority=%u ingress=%u egress=%u\n",
> +		       attr.group, attr.priority, attr.ingress, attr.egress);
> +		return TEST_FAILED;
> +	}
> +
> +	/* Test pattern parsing: eth / ipv4 / end = 3 items */
> +	ret = rte_flow_parser_parse_pattern_str("eth / ipv4 / end",
> +						&pattern, &pattern_n);
> +	if (ret != 0) {
> +		printf("pattern parse failed: %d\n", ret);
> +		return TEST_FAILED;
> +	}
> +	if (pattern_n != 3) {
> +		printf("expected 3 pattern items, got %u\n", pattern_n);
> +		return TEST_FAILED;
> +	}
> +	if (pattern[0].type != RTE_FLOW_ITEM_TYPE_ETH) {
> +		printf("pattern[0] expected ETH, got %d\n", pattern[0].type);
> +		return TEST_FAILED;
> +	}
> +	if (pattern[1].type != RTE_FLOW_ITEM_TYPE_IPV4) {
> +		printf("pattern[1] expected IPV4, got %d\n", pattern[1].type);
> +		return TEST_FAILED;
> +	}
> +	if (pattern[2].type != RTE_FLOW_ITEM_TYPE_END) {
> +		printf("pattern[2] expected END, got %d\n", pattern[2].type);
> +		return TEST_FAILED;
> +	}
> +
> +	/* Test actions parsing with config values: queue index 3 / end = 2 items */
> +	ret = rte_flow_parser_parse_actions_str("queue index 3 / end",
> +						&actions, &actions_n);
> +	if (ret != 0) {
> +		printf("actions parse failed: %d\n", ret);
> +		return TEST_FAILED;
> +	}
> +	if (actions_n != 2) {
> +		printf("expected 2 action items, got %u\n", actions_n);
> +		return TEST_FAILED;
> +	}
> +	if (actions[0].type != RTE_FLOW_ACTION_TYPE_QUEUE) {
> +		printf("actions[0] expected QUEUE, got %d\n", actions[0].type);
> +		return TEST_FAILED;
> +	}
> +	queue_conf = actions[0].conf;
> +	if (queue_conf == NULL || queue_conf->index != 3) {
> +		printf("queue index expected 3, got %u\n",
> +		       queue_conf ? queue_conf->index : 0);
> +		return TEST_FAILED;
> +	}
> +	if (actions[1].type != RTE_FLOW_ACTION_TYPE_END) {
> +		printf("actions[1] expected END, got %d\n", actions[1].type);
> +		return TEST_FAILED;
> +	}
> +
> +	/* Test multiple actions: mark id 42 / drop / end = 3 items */
> +	ret = rte_flow_parser_parse_actions_str("mark id 42 / drop / end",
> +						&actions, &actions_n);
> +	if (ret != 0) {
> +		printf("multi-action parse failed: %d\n", ret);
> +		return TEST_FAILED;
> +	}
> +	if (actions_n != 3) {
> +		printf("expected 3 action items, got %u\n", actions_n);
> +		return TEST_FAILED;
> +	}
> +	if (actions[0].type != RTE_FLOW_ACTION_TYPE_MARK) {
> +		printf("actions[0] expected MARK, got %d\n", actions[0].type);
> +		return TEST_FAILED;
> +	}
> +	mark_conf = actions[0].conf;
> +	if (mark_conf == NULL || mark_conf->id != 42) {
> +		printf("mark id expected 42, got %u\n",
> +		       mark_conf ? mark_conf->id : 0);
> +		return TEST_FAILED;
> +	}
> +	if (actions[1].type != RTE_FLOW_ACTION_TYPE_DROP) {
> +		printf("actions[1] expected DROP, got %d\n", actions[1].type);
> +		return TEST_FAILED;
> +	}
> +	if (actions[2].type != RTE_FLOW_ACTION_TYPE_END) {
> +		printf("actions[2] expected END, got %d\n", actions[2].type);
> +		return TEST_FAILED;
> +	}
> +
> +	/* Test complex pattern: eth / ipv4 / tcp / end = 4 items */
> +	ret = rte_flow_parser_parse_pattern_str("eth / ipv4 / tcp / end",
> +						&pattern, &pattern_n);
> +	if (ret != 0) {
> +		printf("complex pattern parse failed: %d\n", ret);
> +		return TEST_FAILED;
> +	}
> +	if (pattern_n != 4) {
> +		printf("expected 4 pattern items, got %u\n", pattern_n);
> +		return TEST_FAILED;
> +	}
> +	if (pattern[0].type != RTE_FLOW_ITEM_TYPE_ETH ||
> +	    pattern[1].type != RTE_FLOW_ITEM_TYPE_IPV4 ||
> +	    pattern[2].type != RTE_FLOW_ITEM_TYPE_TCP ||
> +	    pattern[3].type != RTE_FLOW_ITEM_TYPE_END) {
> +		printf("complex pattern type mismatch\n");
> +		return TEST_FAILED;
> +	}
> +
> +	return TEST_SUCCESS;
> +}
> +
> +REGISTER_FAST_TEST(flow_parser_autotest, true, true,
> +		   test_flow_parser_command_mapping);
> +
> +REGISTER_FAST_TEST(flow_parser_helpers_autotest, true, true,
> +		   test_flow_parser_lightweight_helpers);

Test looks good.
My preference is for tests to use the macros like TEST_ASSERT() in test.h
since then it makes fixing them easier.

Also if there are multiple tests uses unit test runner instead of one big
test or registering multiple tests.



More information about the dev mailing list