Add flow_api_autotest to exercise the rte_flow API through the null
PMD stub flow ops. Covers basic operations and error handling,
verifying that error types, cause pointers, and messages are
correctly propagated.

Signed-off-by: Stephen Hemminger <[email protected]>
---
 app/test/meson.build     |    1 +
 app/test/test_flow_api.c | 1015 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 1016 insertions(+)
 create mode 100644 app/test/test_flow_api.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 48874037eb..36f34c066b 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -87,6 +87,7 @@ source_file_deps = {
     'test_fib6.c': ['rib', 'fib'],
     'test_fib6_perf.c': ['fib'],
     'test_fib_perf.c': ['net', 'fib'],
+    'test_flow_api.c': ['net_null', 'ethdev', 'bus_vdev'],
     'test_flow_classify.c': ['net', 'acl', 'table', 'ethdev', 'flow_classify'],
     'test_func_reentrancy.c': ['hash', 'lpm'],
     'test_graph.c': ['graph'],
diff --git a/app/test/test_flow_api.c b/app/test/test_flow_api.c
new file mode 100644
index 0000000000..ae6f8e33ea
--- /dev/null
+++ b/app/test/test_flow_api.c
@@ -0,0 +1,1015 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Stephen Hemminger
+ */
+
+/*
+ * Unit tests for the rte_flow generic flow API (rte_flow.h).
+ *
+ * These tests exercise the full rte_flow code path through the null PMD,
+ * which implements flow_ops that validate input and reject all rules with
+ * properly typed rte_flow_error responses.  This lets us verify:
+ *
+ *   1. Specific error types (TYPE_ITEM, TYPE_ACTION, TYPE_ATTR_*, etc.)
+ *      are reported for each class of rejection.
+ *   2. The `cause` pointer in rte_flow_error points at the offending
+ *      pattern item, action, or attribute structure.
+ *   3. Descriptive error messages are propagated to the caller.
+ *   4. Utility functions (rte_flow_error_set, rte_flow_conv) work.
+ *   5. Edge cases (NULL pointers, invalid ports, VOID items) are
+ *      handled without crashes.
+ *
+ * The test requires that net_null is built with null_flow.c (the stub
+ * flow ops).  It is registered as a fast-test and can run with --no-huge.
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <rte_common.h>
+#include <rte_errno.h>
+#include <rte_ethdev.h>
+#include <rte_flow.h>
+#include <rte_malloc.h>
+#include <rte_bus_vdev.h>
+
+#include "test.h"
+
+/* --------------------------------------------------------------------------
+ * Constants
+ * -------------------------------------------------------------------------- 
*/
+
+#define TEST_NULL_VDEV_NAME "net_null_flow_test"
+
+static uint16_t test_port_id;
+static int port_created;
+static struct rte_mempool *mp;
+
+static struct rte_eth_conf port_conf = {
+       .rxmode = {
+               .mq_mode = RTE_ETH_MQ_RX_NONE,
+       },
+       .txmode = {
+               .mq_mode = RTE_ETH_MQ_TX_NONE,
+       },
+};
+
+/* --------------------------------------------------------------------------
+ * Suite setup / teardown
+ * -------------------------------------------------------------------------- 
*/
+
+static int
+testsuite_setup(void)
+{
+       int ret;
+
+       ret = rte_vdev_init(TEST_NULL_VDEV_NAME, "copy=0");
+       if (ret < 0) {
+               printf("TEST-FLOW: failed to create net_null vdev: %s\n",
+                      rte_strerror(-ret));
+               return TEST_SKIPPED;
+       }
+       port_created = 1;
+
+       if (rte_eth_dev_count_avail() == 0) {
+               printf("TEST-FLOW: no available ports after vdev init\n");
+               return TEST_SKIPPED;
+       }
+
+       RTE_ETH_FOREACH_DEV(test_port_id)
+               break;
+
+       mp = rte_pktmbuf_pool_create("flow_test_pool", 256, 32, 0,
+                                    RTE_MBUF_DEFAULT_BUF_SIZE,
+                                    rte_socket_id());
+       if (mp == NULL) {
+               printf("TEST-FLOW: mempool creation failed\n");
+               return TEST_FAILED;
+       }
+
+       ret = rte_eth_dev_configure(test_port_id, 1, 1, &port_conf);
+       if (ret < 0) {
+               printf("TEST-FLOW: port configure failed: %s\n",
+                      rte_strerror(-ret));
+               return TEST_FAILED;
+       }
+
+       ret = rte_eth_rx_queue_setup(test_port_id, 0, 64,
+                                    rte_eth_dev_socket_id(test_port_id),
+                                    NULL, mp);
+       if (ret < 0)
+               return TEST_FAILED;
+
+       ret = rte_eth_tx_queue_setup(test_port_id, 0, 64,
+                                    rte_eth_dev_socket_id(test_port_id),
+                                    NULL);
+       if (ret < 0)
+               return TEST_FAILED;
+
+       ret = rte_eth_dev_start(test_port_id);
+       if (ret < 0)
+               return TEST_FAILED;
+
+       /*
+        * Verify the PMD actually has flow ops.  If it returns -ENOSYS
+        * that means null_flow.c was not linked — skip the suite.
+        */
+       {
+               struct rte_flow_attr attr = { .ingress = 1 };
+               struct rte_flow_item pattern[] = {
+                       { .type = RTE_FLOW_ITEM_TYPE_END },
+               };
+               struct rte_flow_action actions[] = {
+                       { .type = RTE_FLOW_ACTION_TYPE_END },
+               };
+               struct rte_flow_error error;
+
+               ret = rte_flow_validate(test_port_id, &attr,
+                                       pattern, actions, &error);
+               if (ret == -ENOSYS) {
+                       printf("TEST-FLOW: null PMD has no flow_ops "
+                              "(null_flow.c not linked) — skipping\n");
+                       return TEST_SKIPPED;
+               }
+       }
+
+       return TEST_SUCCESS;
+}
+
+static void
+testsuite_teardown(void)
+{
+       if (rte_eth_dev_is_valid_port(test_port_id)) {
+               rte_eth_dev_stop(test_port_id);
+               rte_eth_dev_close(test_port_id);
+       }
+       if (port_created)
+               rte_vdev_uninit(TEST_NULL_VDEV_NAME);
+       if (mp != NULL)
+               rte_mempool_free(mp);
+}
+
+/* --------------------------------------------------------------------------
+ * Helper builders
+ * -------------------------------------------------------------------------- 
*/
+
+static void
+build_eth_ipv4_pattern(struct rte_flow_item pattern[3])
+{
+       memset(pattern, 0, 3 * sizeof(struct rte_flow_item));
+       pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
+       pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
+       pattern[2].type = RTE_FLOW_ITEM_TYPE_END;
+}
+
+static void
+build_drop_actions(struct rte_flow_action actions[2])
+{
+       memset(actions, 0, 2 * sizeof(struct rte_flow_action));
+       actions[0].type = RTE_FLOW_ACTION_TYPE_DROP;
+       actions[1].type = RTE_FLOW_ACTION_TYPE_END;
+}
+
+static void
+build_queue_actions(struct rte_flow_action actions[2],
+                   struct rte_flow_action_queue *q)
+{
+       memset(actions, 0, 2 * sizeof(struct rte_flow_action));
+       memset(q, 0, sizeof(*q));
+       q->index = 0;
+       actions[0].type = RTE_FLOW_ACTION_TYPE_QUEUE;
+       actions[0].conf = q;
+       actions[1].type = RTE_FLOW_ACTION_TYPE_END;
+}
+
+/* ==========================================================================
+ *  Group 1: rte_flow_error_set() utility
+ * ========================================================================== 
*/
+
+static int
+test_error_set_basic(void)
+{
+       struct rte_flow_error error;
+       int dummy = 42;
+       int ret;
+
+       memset(&error, 0x55, sizeof(error));
+
+       ret = rte_flow_error_set(&error, EINVAL,
+                                RTE_FLOW_ERROR_TYPE_ATTR,
+                                &dummy,
+                                "test error message");
+
+       RTE_TEST_ASSERT(ret < 0,
+                       "error_set should return negative");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ATTR,
+                             "error type mismatch");
+       RTE_TEST_ASSERT(error.cause == &dummy,
+                       "cause pointer mismatch");
+       RTE_TEST_ASSERT(error.message != NULL &&
+                       strcmp(error.message, "test error message") == 0,
+                       "message mismatch");
+       RTE_TEST_ASSERT_EQUAL(rte_errno, EINVAL,
+                             "rte_errno mismatch");
+
+       /* NULL error pointer — must not crash. */
+       ret = rte_flow_error_set(NULL, ENOTSUP,
+                                RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+                                NULL, NULL);
+       RTE_TEST_ASSERT(ret < 0,
+                       "error_set(NULL) should return negative");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_error_set_type_none(void)
+{
+       struct rte_flow_error error;
+
+       memset(&error, 0xFF, sizeof(error));
+       rte_flow_error_set(&error, 0,
+                          RTE_FLOW_ERROR_TYPE_NONE,
+                          NULL, NULL);
+
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_NONE,
+                             "type should be NONE");
+       RTE_TEST_ASSERT(error.cause == NULL, "cause should be NULL");
+       RTE_TEST_ASSERT(error.message == NULL, "message should be NULL");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_error_set_all_types(void)
+{
+       struct rte_flow_error error;
+       static const enum rte_flow_error_type types[] = {
+               RTE_FLOW_ERROR_TYPE_NONE,
+               RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+               RTE_FLOW_ERROR_TYPE_HANDLE,
+               RTE_FLOW_ERROR_TYPE_ATTR_GROUP,
+               RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
+               RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
+               RTE_FLOW_ERROR_TYPE_ATTR_EGRESS,
+               RTE_FLOW_ERROR_TYPE_ATTR,
+               RTE_FLOW_ERROR_TYPE_ITEM_NUM,
+               RTE_FLOW_ERROR_TYPE_ITEM_SPEC,
+               RTE_FLOW_ERROR_TYPE_ITEM_LAST,
+               RTE_FLOW_ERROR_TYPE_ITEM_MASK,
+               RTE_FLOW_ERROR_TYPE_ITEM,
+               RTE_FLOW_ERROR_TYPE_ACTION_NUM,
+               RTE_FLOW_ERROR_TYPE_ACTION_CONF,
+               RTE_FLOW_ERROR_TYPE_ACTION,
+       };
+       unsigned int i;
+
+       for (i = 0; i < RTE_DIM(types); i++) {
+               memset(&error, 0xFF, sizeof(error));
+               rte_flow_error_set(&error, EINVAL, types[i], NULL, NULL);
+               RTE_TEST_ASSERT_EQUAL((int)error.type, (int)types[i],
+                                     "type mismatch at index %u", i);
+       }
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 2: validate — attribute rejection
+ * ========================================================================== 
*/
+
+static int
+test_validate_no_direction(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "should reject no-direction rule");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ATTR,
+                             "error type should be ATTR");
+       RTE_TEST_ASSERT(error.message != NULL,
+                       "error message should be set");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_transfer_rejected(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.transfer = 1;
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "transfer should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ATTR_TRANSFER,
+                             "should be ATTR_TRANSFER");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_group_rejected(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+       attr.group = 5;
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "group > 0 should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ATTR_GROUP,
+                             "should be ATTR_GROUP");
+       RTE_TEST_ASSERT_EQUAL(rte_errno, ENOTSUP, "errno should be ENOTSUP");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_priority_rejected(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+       attr.priority = 7;
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "priority > 0 should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ATTR_PRIORITY,
+                             "should be ATTR_PRIORITY");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 3: validate — pattern item rejection with cause pointer
+ * ========================================================================== 
*/
+
+static int
+test_validate_item_rejected(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "ETH item should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ITEM,
+                             "should be TYPE_ITEM");
+       RTE_TEST_ASSERT(error.cause == &pattern[0],
+                       "cause should point at pattern[0] (ETH)");
+       RTE_TEST_ASSERT_EQUAL(rte_errno, ENOTSUP, "errno should be ENOTSUP");
+       RTE_TEST_ASSERT(error.message != NULL, "should have a message");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_void_then_real_item(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[5];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+
+       memset(pattern, 0, sizeof(pattern));
+       pattern[0].type = RTE_FLOW_ITEM_TYPE_VOID;
+       pattern[1].type = RTE_FLOW_ITEM_TYPE_ETH;
+       pattern[2].type = RTE_FLOW_ITEM_TYPE_IPV4;
+       pattern[3].type = RTE_FLOW_ITEM_TYPE_END;
+
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ITEM,
+                             "should be TYPE_ITEM");
+       RTE_TEST_ASSERT(error.cause == &pattern[1],
+                       "cause should skip VOID, point at ETH (pattern[1])");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_ipv4_with_spec_mask(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[4];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       struct rte_flow_item_eth eth_spec, eth_mask;
+       struct rte_flow_item_ipv4 ipv4_spec, ipv4_mask;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+
+       memset(&eth_spec, 0, sizeof(eth_spec));
+       memset(&eth_mask, 0, sizeof(eth_mask));
+       eth_spec.hdr.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
+       eth_mask.hdr.ether_type = 0xFFFF;
+
+       memset(&ipv4_spec, 0, sizeof(ipv4_spec));
+       memset(&ipv4_mask, 0, sizeof(ipv4_mask));
+       ipv4_spec.hdr.dst_addr = rte_cpu_to_be_32(0xC0A80001);
+       ipv4_mask.hdr.dst_addr = 0xFFFFFFFF;
+
+       memset(pattern, 0, sizeof(pattern));
+       pattern[0].type = RTE_FLOW_ITEM_TYPE_ETH;
+       pattern[0].spec = &eth_spec;
+       pattern[0].mask = &eth_mask;
+       pattern[1].type = RTE_FLOW_ITEM_TYPE_IPV4;
+       pattern[1].spec = &ipv4_spec;
+       pattern[1].mask = &ipv4_mask;
+       pattern[2].type = RTE_FLOW_ITEM_TYPE_END;
+
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ITEM,
+                             "should be TYPE_ITEM");
+       RTE_TEST_ASSERT(error.cause == &pattern[0],
+                       "cause should point at first real item");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 4: validate — action rejection with cause pointer
+ * ========================================================================== 
*/
+
+static int
+test_validate_action_drop_rejected(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[2];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+
+       memset(pattern, 0, sizeof(pattern));
+       pattern[0].type = RTE_FLOW_ITEM_TYPE_END;
+
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "DROP should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ACTION,
+                             "should be TYPE_ACTION");
+       RTE_TEST_ASSERT(error.cause == &actions[0],
+                       "cause should point at the DROP action");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_action_queue_rejected(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[2];
+       struct rte_flow_action actions[2];
+       struct rte_flow_action_queue queue_conf;
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+
+       memset(pattern, 0, sizeof(pattern));
+       pattern[0].type = RTE_FLOW_ITEM_TYPE_END;
+
+       build_queue_actions(actions, &queue_conf);
+       memset(&error, 0, sizeof(error));
+
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "QUEUE should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ACTION,
+                             "should be TYPE_ACTION");
+       RTE_TEST_ASSERT(error.cause == &actions[0],
+                       "cause should point at the QUEUE action");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_void_only_rejected(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[2];
+       struct rte_flow_action actions[3];
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+
+       memset(pattern, 0, sizeof(pattern));
+       pattern[0].type = RTE_FLOW_ITEM_TYPE_END;
+
+       memset(actions, 0, sizeof(actions));
+       actions[0].type = RTE_FLOW_ACTION_TYPE_VOID;
+       actions[1].type = RTE_FLOW_ACTION_TYPE_END;
+
+       memset(&error, 0, sizeof(error));
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "VOID-only should still be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+                             "should be UNSPECIFIED (generic reject)");
+       RTE_TEST_ASSERT_EQUAL(rte_errno, ENOTSUP, "errno should be ENOTSUP");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_action_mark_first(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[2];
+       struct rte_flow_action actions[4];
+       struct rte_flow_action_mark mark_conf;
+       struct rte_flow_action_queue queue_conf;
+       struct rte_flow_error error;
+       int ret;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+
+       memset(pattern, 0, sizeof(pattern));
+       pattern[0].type = RTE_FLOW_ITEM_TYPE_END;
+
+       memset(actions, 0, sizeof(actions));
+       memset(&mark_conf, 0, sizeof(mark_conf));
+       memset(&queue_conf, 0, sizeof(queue_conf));
+       mark_conf.id = 0xBEEF;
+       queue_conf.index = 0;
+       actions[0].type = RTE_FLOW_ACTION_TYPE_MARK;
+       actions[0].conf = &mark_conf;
+       actions[1].type = RTE_FLOW_ACTION_TYPE_QUEUE;
+       actions[1].conf = &queue_conf;
+       actions[2].type = RTE_FLOW_ACTION_TYPE_END;
+
+       memset(&error, 0, sizeof(error));
+       ret = rte_flow_validate(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "MARK+QUEUE should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ACTION,
+                             "should be TYPE_ACTION");
+       RTE_TEST_ASSERT(error.cause == &actions[0],
+                       "cause should point at MARK (first non-VOID)");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 5: rte_flow_create()
+ * ========================================================================== 
*/
+
+static int
+test_create_returns_null(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+       struct rte_flow *flow;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.ingress = 1;
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       flow = rte_flow_create(test_port_id, &attr, pattern, actions, &error);
+
+       RTE_TEST_ASSERT(flow == NULL, "create should return NULL");
+       RTE_TEST_ASSERT(error.type != RTE_FLOW_ERROR_TYPE_NONE,
+                       "error type should be set");
+       RTE_TEST_ASSERT(error.message != NULL, "should have a message");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_create_invalid_port(void)
+{
+       struct rte_flow_attr attr = { .ingress = 1 };
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       RTE_TEST_ASSERT(rte_flow_create(RTE_MAX_ETHPORTS, &attr,
+                                       pattern, actions, &error) == NULL,
+                       "create must fail on invalid port");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_create_null_error(void)
+{
+       struct rte_flow_attr attr = { .ingress = 1 };
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+
+       RTE_TEST_ASSERT(rte_flow_create(test_port_id, &attr,
+                                       pattern, actions, NULL) == NULL,
+                       "create(null error) should return NULL");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 6: rte_flow_destroy()
+ * ========================================================================== 
*/
+
+static int
+test_destroy_null_handle(void)
+{
+       struct rte_flow_error error;
+
+       memset(&error, 0, sizeof(error));
+       int ret = rte_flow_destroy(test_port_id, NULL, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "destroy(NULL) should fail");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_HANDLE,
+                             "should be TYPE_HANDLE");
+       RTE_TEST_ASSERT_EQUAL(rte_errno, ENOENT, "errno should be ENOENT");
+       RTE_TEST_ASSERT(error.message != NULL, "should have a message");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_destroy_invalid_port(void)
+{
+       struct rte_flow_error error;
+
+       memset(&error, 0, sizeof(error));
+       RTE_TEST_ASSERT(rte_flow_destroy(RTE_MAX_ETHPORTS, NULL, &error) != 0,
+                       "destroy should fail on bad port");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 7: rte_flow_flush()
+ * ========================================================================== 
*/
+
+static int
+test_flush_succeeds(void)
+{
+       struct rte_flow_error error;
+
+       memset(&error, 0, sizeof(error));
+       RTE_TEST_ASSERT_EQUAL(rte_flow_flush(test_port_id, &error), 0,
+                             "flush should succeed (nothing to flush)");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_flush_invalid_port(void)
+{
+       struct rte_flow_error error;
+
+       memset(&error, 0, sizeof(error));
+       RTE_TEST_ASSERT(rte_flow_flush(RTE_MAX_ETHPORTS, &error) != 0,
+                       "flush bad port should fail");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_flush_null_error(void)
+{
+       RTE_TEST_ASSERT_EQUAL(rte_flow_flush(test_port_id, NULL), 0,
+                             "flush(null error) should succeed");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 8: rte_flow_query()
+ * ========================================================================== 
*/
+
+static int
+test_query_rejected(void)
+{
+       struct rte_flow_action action = {
+               .type = RTE_FLOW_ACTION_TYPE_COUNT,
+       };
+       struct rte_flow_query_count count;
+       struct rte_flow_error error;
+
+       memset(&count, 0, sizeof(count));
+       memset(&error, 0, sizeof(error));
+
+       int ret = rte_flow_query(test_port_id, NULL, &action, &count, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "query should fail");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+                             "should be UNSPECIFIED");
+       RTE_TEST_ASSERT_EQUAL(rte_errno, ENOTSUP, "errno should be ENOTSUP");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_query_invalid_port(void)
+{
+       struct rte_flow_action action = {
+               .type = RTE_FLOW_ACTION_TYPE_COUNT,
+       };
+       struct rte_flow_query_count count;
+       struct rte_flow_error error;
+
+       memset(&count, 0, sizeof(count));
+       memset(&error, 0, sizeof(error));
+
+       RTE_TEST_ASSERT(rte_flow_query(RTE_MAX_ETHPORTS, NULL,
+                                      &action, &count, &error) != 0,
+                       "query bad port should fail");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 9: rte_flow_isolate()
+ * ========================================================================== 
*/
+
+static int
+test_isolate_rejected(void)
+{
+       struct rte_flow_error error;
+
+       memset(&error, 0, sizeof(error));
+       int ret = rte_flow_isolate(test_port_id, 1, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "isolate should fail");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+                             "should be UNSPECIFIED");
+       RTE_TEST_ASSERT_EQUAL(rte_errno, ENOTSUP, "errno should be ENOTSUP");
+
+       memset(&error, 0, sizeof(error));
+       RTE_TEST_ASSERT(rte_flow_isolate(test_port_id, 0, &error) != 0,
+                       "isolate(0) should also fail");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_isolate_invalid_port(void)
+{
+       struct rte_flow_error error;
+
+       memset(&error, 0, sizeof(error));
+       RTE_TEST_ASSERT(rte_flow_isolate(RTE_MAX_ETHPORTS, 1, &error) != 0,
+                       "isolate bad port should fail");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 10: rte_flow_conv() utilities
+ * ========================================================================== 
*/
+
+static int
+test_conv_item_name(void)
+{
+       const char *name = NULL;
+
+       int ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
+                               &name, sizeof(name),
+                               (void *)(uintptr_t)RTE_FLOW_ITEM_TYPE_ETH,
+                               NULL);
+       RTE_TEST_ASSERT(ret > 0, "conv ETH should succeed");
+       RTE_TEST_ASSERT(name != NULL, "name should be non-NULL");
+
+       name = NULL;
+       rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name, sizeof(name),
+                     (void *)(uintptr_t)RTE_FLOW_ITEM_TYPE_IPV4, NULL);
+       RTE_TEST_ASSERT(name != NULL, "IPV4 name should be non-NULL");
+
+       name = NULL;
+       rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name, sizeof(name),
+                     (void *)(uintptr_t)RTE_FLOW_ITEM_TYPE_END, NULL);
+       RTE_TEST_ASSERT(name != NULL, "END name should be non-NULL");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_conv_action_name(void)
+{
+       const char *name = NULL;
+
+       int ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+                               &name, sizeof(name),
+                               (void *)(uintptr_t)RTE_FLOW_ACTION_TYPE_DROP,
+                               NULL);
+       RTE_TEST_ASSERT(ret > 0, "conv DROP should succeed");
+       RTE_TEST_ASSERT(name != NULL, "DROP name should be non-NULL");
+
+       name = NULL;
+       rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name, sizeof(name),
+                     (void *)(uintptr_t)RTE_FLOW_ACTION_TYPE_QUEUE, NULL);
+       RTE_TEST_ASSERT(name != NULL, "QUEUE name should be non-NULL");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_conv_zero_len(void)
+{
+       int ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
+                               NULL, 0,
+                               (void *)(uintptr_t)RTE_FLOW_ITEM_TYPE_ETH,
+                               NULL);
+       RTE_TEST_ASSERT(ret != 0, "should return non-zero");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Group 11: Egress direction and NULL error pointer
+ * ========================================================================== 
*/
+
+static int
+test_validate_egress(void)
+{
+       struct rte_flow_attr attr;
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+       struct rte_flow_error error;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.egress = 1;
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+       memset(&error, 0, sizeof(error));
+
+       int ret = rte_flow_validate(test_port_id, &attr,
+                                   pattern, actions, &error);
+
+       RTE_TEST_ASSERT(ret != 0, "egress rule should be rejected");
+       RTE_TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_ITEM,
+                             "should be TYPE_ITEM for egress too");
+
+       return TEST_SUCCESS;
+}
+
+static int
+test_validate_null_error(void)
+{
+       struct rte_flow_attr attr = { .ingress = 1 };
+       struct rte_flow_item pattern[3];
+       struct rte_flow_action actions[2];
+
+       build_eth_ipv4_pattern(pattern);
+       build_drop_actions(actions);
+
+       RTE_TEST_ASSERT(rte_flow_validate(test_port_id, &attr,
+                                         pattern, actions, NULL) != 0,
+                       "validate(null error) should still fail");
+
+       return TEST_SUCCESS;
+}
+
+/* ==========================================================================
+ *  Suite definition
+ * ========================================================================== 
*/
+
+static struct unit_test_suite flow_api_testsuite = {
+       .suite_name = "rte_flow API unit tests",
+       .setup = testsuite_setup,
+       .teardown = testsuite_teardown,
+       .unit_test_cases = {
+               /* Error utility */
+               TEST_CASE(test_error_set_basic),
+               TEST_CASE(test_error_set_type_none),
+               TEST_CASE(test_error_set_all_types),
+
+               /* Validate — attributes */
+               TEST_CASE(test_validate_no_direction),
+               TEST_CASE(test_validate_transfer_rejected),
+               TEST_CASE(test_validate_group_rejected),
+               TEST_CASE(test_validate_priority_rejected),
+               TEST_CASE(test_validate_egress),
+               TEST_CASE(test_validate_null_error),
+
+               /* Validate — pattern items */
+               TEST_CASE(test_validate_item_rejected),
+               TEST_CASE(test_validate_void_then_real_item),
+               TEST_CASE(test_validate_ipv4_with_spec_mask),
+
+               /* Validate — actions */
+               TEST_CASE(test_validate_action_drop_rejected),
+               TEST_CASE(test_validate_action_queue_rejected),
+               TEST_CASE(test_validate_void_only_rejected),
+               TEST_CASE(test_validate_action_mark_first),
+
+               /* Create */
+               TEST_CASE(test_create_returns_null),
+               TEST_CASE(test_create_invalid_port),
+               TEST_CASE(test_create_null_error),
+
+               /* Destroy */
+               TEST_CASE(test_destroy_null_handle),
+               TEST_CASE(test_destroy_invalid_port),
+
+               /* Flush */
+               TEST_CASE(test_flush_succeeds),
+               TEST_CASE(test_flush_invalid_port),
+               TEST_CASE(test_flush_null_error),
+
+               /* Query */
+               TEST_CASE(test_query_rejected),
+               TEST_CASE(test_query_invalid_port),
+
+               /* Isolate */
+               TEST_CASE(test_isolate_rejected),
+               TEST_CASE(test_isolate_invalid_port),
+
+               /* Conv utilities */
+               TEST_CASE(test_conv_item_name),
+               TEST_CASE(test_conv_action_name),
+               TEST_CASE(test_conv_zero_len),
+
+               TEST_CASES_END(),
+       },
+};
+
+static int
+test_flow_api(void)
+{
+       return unit_test_suite_runner(&flow_api_testsuite);
+}
+
+REGISTER_FAST_TEST(flow_api_autotest, NOHUGE_OK, ASAN_OK, test_flow_api);
-- 
2.51.0

Reply via email to