| # Copyright 2013, Big Switch Networks, Inc. |
| # |
| # LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with |
| # the following special exception: |
| # |
| # LOXI Exception |
| # |
| # As a special exception to the terms of the EPL, you may distribute libraries |
| # generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided |
| # that copyright and licensing notices generated by LoxiGen are not altered or removed |
| # from the LoxiGen Libraries and the notice provided below is (i) included in |
| # the LoxiGen Libraries, if distributed in source code form and (ii) included in any |
| # documentation for the LoxiGen Libraries, if distributed in binary form. |
| # |
| # Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler." |
| # |
| # You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain |
| # a copy of the EPL at: |
| # |
| # http://www.eclipse.org/legal/epl-v10.html |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # EPL for the specific language governing permissions and limitations |
| # under the EPL. |
| |
| """ |
| @brief Test case generation functions |
| |
| @fixme Update the following |
| The following components are generated. |
| |
| test_common.[ch]: A collection of common code for tests. Currently |
| this includes the ability to set the scalar members of an object with |
| incrementing values and then similarly verify those values |
| |
| test_scalar_acc.c: Instantiate each type of object, then set and get |
| scalar values in the objects. |
| |
| test_list.c: Instantiate each type of list, add an element of each |
| type the list supports, setting scalar values of the elements. |
| |
| test_match.c: Various tests for match objects |
| |
| test_msg.c: Instantiate top level messages |
| |
| These will move towards unified tests that do the following: |
| |
| Create or init an object. |
| Populate the object with incrementing values. |
| Possibly transform the object in some way (e.g., run the underlying |
| wire buffer through a parse routine). |
| Verify that the members all have the appropriate value |
| |
| Through out, checking the consistency of memory and memory operations |
| is done with mcheck (if available). |
| |
| """ |
| |
| import sys |
| import c_gen.of_g_legacy as of_g |
| import c_gen.match as match |
| import c_gen.flags as flags |
| from generic_utils import * |
| import c_gen.type_maps as type_maps |
| import c_gen.loxi_utils_legacy as loxi_utils |
| import c_gen.identifiers as identifiers |
| import util |
| import test_data |
| import loxi_globals |
| from loxi_ir import * |
| |
| def var_name_map(m_type): |
| """ |
| Map a type to a generic variable name for the type. |
| @param m_type The data type |
| |
| Used mostly in test code generation, but also for the dup functions. |
| """ |
| _var_name_map= dict( |
| uint8_t="val8", |
| uint16_t="val16", |
| uint32_t="val32", |
| uint64_t="val64", |
| of_ipv4_t="ipv4", |
| of_port_no_t="port_no", |
| of_fm_cmd_t="fm_cmd", |
| of_wc_bmap_t="wc_bmap", |
| of_match_bmap_t = "match_bmap", |
| of_port_name_t="port_name", |
| of_table_name_t="table_name", |
| of_desc_str_t="desc_str", |
| of_serial_num_t="ser_num", |
| of_str64_t="str64", |
| of_mac_addr_t="mac_addr", |
| of_ipv6_t="ipv6", |
| # Non-scalars; more TBD |
| of_octets_t="octets", |
| of_meter_features_t="features", |
| of_match_t="match", |
| of_oxm_header_t="oxm", |
| of_bsn_vport_header_t="bsn_vport", |
| of_table_desc_t="table_desc", |
| # BSN extensions |
| of_bsn_vport_q_in_q_t="vport", |
| of_bitmap_128_t="bitmap_128", |
| of_checksum_128_t="checksum_128", |
| ) |
| |
| if m_type.find("of_list_") == 0: |
| return "list" |
| if m_type in of_g.of_mixed_types: |
| return of_g.of_mixed_types[m_type]["short_name"] |
| return _var_name_map[m_type] |
| |
| integer_types = ["uint8_t", "uint16_t", "uint32_t", "uint64_t", |
| "of_port_no_t", "of_fm_cmd_t", "of_wc_bmap_t", |
| "of_match_bmap_t", "of_ipv4_t"] |
| string_types = [ "of_port_name_t", "of_table_name_t", |
| "of_desc_str_t", "of_serial_num_t", "of_mac_addr_t", |
| "of_ipv6_t", "of_bitmap_128_t", "of_checksum_128_t", |
| "of_str64_t"] |
| |
| scalar_types = integer_types[:] |
| scalar_types.extend(string_types) |
| |
| # When embedding an object inside of another object we have to pick a single |
| # subclass to use, unlike lists where we use all subclasses. |
| embedded_subclasses = { |
| 'of_oxm_header_t': 'of_oxm_eth_type', |
| 'of_bsn_vport_header_t': 'of_bsn_vport_q_in_q', |
| } |
| |
| def ignore_member(cls, version, m_name, m_type): |
| """ |
| Filter out names or types that either don't have accessors |
| or those that should not be messed with |
| or whose types we're not ready to deal with yet. |
| """ |
| |
| uclass = loxi_globals.unified.class_by_name(cls) |
| if not uclass: |
| return True |
| |
| if not isinstance(uclass.member_by_name(m_name), OFDataMember): |
| return True |
| |
| return loxi_utils.skip_member_name(m_name) or m_type not in scalar_types |
| |
| def gen_fill_string(out): |
| out.write(""" |
| |
| /** |
| * The increment to use on values inside a string |
| */ |
| #define OF_TEST_STR_INCR 3 |
| |
| /** |
| * Fill in a buffer with incrementing values starting |
| * at the given offset with the given value |
| * @param buf The buffer to fill |
| * @param value The value to use for data |
| * @param len The number of bytes to fill |
| */ |
| |
| void |
| of_test_str_fill(uint8_t *buf, int value, int len) |
| { |
| int i; |
| |
| for (i = 0; i < len; i++) { |
| *buf = value; |
| value += OF_TEST_STR_INCR; |
| buf++; |
| } |
| } |
| |
| /** |
| * Given a buffer, verify that it's filled as above |
| * @param buf The buffer to check |
| * @param value The value to use for data |
| * @param len The number of bytes to fill |
| * @return Boolean True on equality (success) |
| */ |
| |
| int |
| of_test_str_check(uint8_t *buf, int value, int len) |
| { |
| int i; |
| uint8_t val8; |
| |
| val8 = value; |
| |
| for (i = 0; i < len; i++) { |
| if (*buf != val8) { |
| return 0; |
| } |
| val8 += OF_TEST_STR_INCR; |
| buf++; |
| } |
| |
| return 1; |
| } |
| |
| /** |
| * Global that determines how octets should be populated |
| * -1 means use value % MAX (below) to determine length |
| * 0, 1, ... means used that fixed length |
| * |
| * Note: Was 16K, but that made objects too big. May add flexibility |
| * to call populate with a max parameter for length |
| */ |
| int octets_pop_style = -1; |
| #define OCTETS_MAX_VALUE (128) /* 16K was too big */ |
| #define OCTETS_MULTIPLIER 6367 /* A prime */ |
| |
| int |
| of_octets_populate(of_octets_t *octets, int value) |
| { |
| if (octets_pop_style < 0) { |
| octets->bytes = (value * OCTETS_MULTIPLIER) % OCTETS_MAX_VALUE; |
| } else { |
| octets->bytes = octets_pop_style; |
| } |
| |
| if (octets->bytes != 0) { |
| if ((octets->data = (uint8_t *)MALLOC(octets->bytes)) == NULL) { |
| return 0; |
| } |
| of_test_str_fill(octets->data, value, octets->bytes); |
| value += 1; |
| } |
| |
| return value; |
| } |
| |
| int |
| of_octets_check(of_octets_t *octets, int value) |
| { |
| int len; |
| |
| if (octets_pop_style < 0) { |
| len = (value * OCTETS_MULTIPLIER) % OCTETS_MAX_VALUE; |
| TEST_ASSERT(octets->bytes == len); |
| } else { |
| TEST_ASSERT(octets->bytes == octets_pop_style); |
| } |
| |
| if (octets->bytes != 0) { |
| TEST_ASSERT(of_test_str_check(octets->data, value, octets->bytes) |
| == 1); |
| value += 1; |
| } |
| |
| return value; |
| } |
| |
| int |
| of_match_populate(of_match_t *match, of_version_t version, int value) |
| { |
| MEMSET(match, 0, sizeof(*match)); |
| match->version = version; |
| """) |
| |
| def populate_match_version(wire_version, keys): |
| out.write(""" |
| if (version == %d) {\ |
| """ % wire_version) |
| for key in keys: |
| entry = match.of_match_members[key] |
| out.write(""" |
| OF_MATCH_MASK_%(ku)s_EXACT_SET(match); |
| VAR_%(u_type)s_INIT(match->fields.%(key)s, value); |
| value += 1; |
| """ % dict(key=key, u_type=entry["m_type"].upper(), ku=key.upper())) |
| out.write(""" |
| } |
| |
| """) |
| |
| for wire_version, match_keys in match.match_keys.items(): |
| populate_match_version(wire_version, match_keys) |
| |
| out.write(""" |
| if (value % 2) { |
| /* Sometimes set ipv4 addr masks to non-exact */ |
| match->masks.ipv4_src = 0xffff0000; |
| match->masks.ipv4_dst = 0xfffff800; |
| } |
| |
| /* Restrict values according to masks */ |
| of_memmask(&match->fields, &match->masks, sizeof(match->fields)); |
| return value; |
| } |
| |
| int |
| of_match_check(of_match_t *match, of_version_t version, int value) |
| { |
| of_match_t check; |
| |
| value = of_match_populate(&check, match->version, value); |
| TEST_ASSERT(value != 0); |
| TEST_ASSERT(MEMCMP(match, &check, sizeof(check)) == 0); |
| |
| return value; |
| } |
| """) |
| |
| def gen_common_test_header(out, name): |
| loxi_utils.gen_c_copy_license(out) |
| out.write(""" |
| /* |
| * Test header file |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| */ |
| |
| #if !defined(_TEST_COMMON_H_) |
| #define _TEST_COMMON_H_ |
| |
| #define DISABLE_WARN_UNUSED_RESULT |
| #include <loci/loci.h> |
| #include <locitest/of_dup.h> |
| #include <locitest/unittest.h> |
| |
| extern int global_error; |
| extern int exit_on_error; |
| |
| /* @todo Make option for -k to continue tests if errors */ |
| #define RUN_TEST(test) do { \\ |
| int rv; \\ |
| TESTCASE(test, rv); \\ |
| if (rv != TEST_PASS) { \\ |
| global_error=1; \\ |
| if (exit_on_error) return(1); \\ |
| } \\ |
| } while(0) |
| |
| #define TEST_OK(op) TEST_ASSERT((op) == OF_ERROR_NONE) |
| #define TEST_INDIGO_OK(op) TEST_ASSERT((op) == INDIGO_ERROR_NONE) |
| |
| /* |
| * Declarations of functions to populate scalar values in a a class |
| */ |
| |
| extern void of_test_str_fill(uint8_t *buf, int value, int len); |
| extern int of_test_str_check(uint8_t *buf, int value, int len); |
| |
| |
| extern int of_octets_populate(of_octets_t *octets, int value); |
| extern int of_octets_check(of_octets_t *octets, int value); |
| extern int of_match_populate(of_match_t *match, of_version_t version, |
| int value); |
| extern int of_match_check(of_match_t *match, of_version_t version, int value); |
| extern int test_ident_macros(void); |
| extern int test_dump_objs(void); |
| |
| /* In test_match_utils.c */ |
| extern int test_match_utils(void); |
| |
| extern int run_unified_accessor_tests(void); |
| extern int run_match_tests(void); |
| extern int run_utility_tests(void); |
| |
| extern int run_scalar_acc_tests(void); |
| extern int run_list_tests(void); |
| extern int run_message_tests(void); |
| |
| extern int run_validator_tests(void); |
| |
| extern int run_list_limits_tests(void); |
| |
| extern int test_ext_objs(void); |
| extern int test_datafiles(void); |
| |
| """) |
| |
| for version in of_g.of_version_range: |
| for cls in of_g.standard_class_order: |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| out.write(""" |
| extern int %(cls)s_%(v_name)s_populate( |
| %(cls)s_t *obj, int value); |
| extern int %(cls)s_%(v_name)s_check( |
| %(cls)s_t *obj, int value); |
| extern int %(cls)s_%(v_name)s_populate_scalars( |
| %(cls)s_t *obj, int value); |
| extern int %(cls)s_%(v_name)s_check_scalars( |
| %(cls)s_t *obj, int value); |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| |
| out.write(""" |
| /* |
| * Declarations for list population and check primitives |
| */ |
| """) |
| |
| for version in of_g.of_version_range: |
| for cls in of_g.ordered_list_objects: |
| if version in of_g.unified[cls]: |
| out.write(""" |
| extern int |
| list_setup_%(cls)s_%(v_name)s( |
| %(cls)s_t *list, int value); |
| extern int |
| list_check_%(cls)s_%(v_name)s( |
| %(cls)s_t *list, int value); |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| |
| out.write("\n#endif /* _TEST_COMMON_H_ */\n") |
| |
| def gen_common_test(out, name): |
| """ |
| Generate common test content including main |
| """ |
| loxi_utils.gen_c_copy_license(out) |
| out.write(""" |
| /* |
| * Common test code for LOCI |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| */ |
| |
| #define DISABLE_WARN_UNUSED_RESULT |
| #include "loci_log.h" |
| #include <loci/loci_obj_dump.h> |
| #include <locitest/unittest.h> |
| #include <locitest/test_common.h> |
| |
| /* mcheck is a glibc extension */ |
| #if defined(__linux__) |
| #include <mcheck.h> |
| #define MCHECK_INIT mcheck(NULL) |
| #else |
| #define MCHECK_INIT do { } while (0) |
| #endif |
| |
| /** |
| * Exit on error if set to 1 |
| */ |
| int exit_on_error = 1; |
| |
| /** |
| * Global error state: 0 is okay, 1 is error |
| */ |
| int global_error = 0; |
| |
| extern int run_unified_accessor_tests(void); |
| extern int run_match_tests(void); |
| extern int run_utility_tests(void); |
| |
| extern int run_scalar_acc_tests(void); |
| extern int run_list_tests(void); |
| extern int run_message_tests(void); |
| |
| /** |
| * Macros for initializing and checking scalar types |
| * |
| * @param var The variable being initialized or checked |
| * @param val The integer value to set/check against, see below |
| * |
| * Note that equality means something special for strings. Each byte |
| * is initialized to an incrementing value. So check is done against that. |
| * |
| */ |
| |
| """) |
| for t in scalar_types: |
| if t in integer_types: |
| out.write(""" |
| #define VAR_%s_INIT(var, val) var = (%s)(val) |
| #define VAR_%s_CHECK(var, val) ((var) == (%s)(val)) |
| """ % (t.upper(), t, t.upper(), t)) |
| else: |
| out.write(""" |
| #define VAR_%s_INIT(var, val) \\ |
| of_test_str_fill((uint8_t *)&(var), val, sizeof(var)) |
| #define VAR_%s_CHECK(var, val) \\ |
| of_test_str_check((uint8_t *)&(var), val, sizeof(var)) |
| """ % (t.upper(), t.upper())) |
| |
| gen_fill_string(out) |
| gen_scalar_set_check_funs(out) |
| gen_list_set_check_funs(out) |
| gen_unified_accessor_funs(out) |
| |
| gen_ident_tests(out) |
| gen_log_test(out) |
| |
| def gen_message_scalar_test(out, name): |
| """ |
| Generate test cases for message objects, scalar accessors |
| """ |
| |
| loxi_utils.gen_c_copy_license(out) |
| out.write(""" |
| /** |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| * |
| * Message-scalar tests for all versions |
| */ |
| |
| #include <locitest/test_common.h> |
| """) |
| for version in of_g.of_version_range: |
| v_name = loxi_utils.version_to_name(version) |
| out.write(""" |
| /** |
| * Message-scalar tests for version %s |
| */ |
| """ % v_name) |
| for cls in of_g.standard_class_order: |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| if version in of_g.unified[cls]: |
| message_scalar_test(out, version, cls) |
| |
| out.write(""" |
| int |
| run_scalar_acc_tests(void) |
| { |
| """) |
| for version in of_g.of_version_range: |
| v_name = loxi_utils.version_to_name(version) |
| for cls in of_g.standard_class_order: |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| if version in of_g.unified[cls]: |
| test_name = "%s_%s" % (cls, v_name) |
| out.write(" RUN_TEST(%s_scalar);\n" % test_name) |
| |
| out.write(" return TEST_PASS;\n}\n"); |
| |
| def message_scalar_test(out, version, cls): |
| """ |
| Generate one test case for the given version and class |
| """ |
| |
| members, member_types = scalar_member_types_get(cls, version) |
| length = of_g.base_length[(cls, version)] |
| v_name = loxi_utils.version_to_name(version) |
| |
| out.write(""" |
| static int |
| test_%(cls)s_%(v_name)s_scalar(void) |
| { |
| %(cls)s_t *obj; |
| |
| obj = %(cls)s_new(%(v_name)s); |
| TEST_ASSERT(obj != NULL); |
| TEST_ASSERT(obj->version == %(v_name)s); |
| TEST_ASSERT(obj->length == %(length)d); |
| TEST_ASSERT(obj->parent == NULL); |
| TEST_ASSERT(obj->object_id == %(u_cls)s); |
| """ % dict(cls=cls, u_cls=cls.upper(), |
| v_name=v_name, length=length, version=version)) |
| |
| # If this class is a concrete member of an inheritance hierarchy, |
| # run the hierarchy's root wire type parser and assert it returns |
| # the expected object id. |
| ofclass = loxi_globals.unified.class_by_name(cls) |
| if ofclass and not ofclass.virtual: |
| root = ofclass |
| while root.superclass: |
| root = root.superclass |
| if root.virtual: |
| out.write(""" |
| { |
| of_object_id_t object_id; |
| %(root_cls)s_wire_object_id_get(obj, &object_id); |
| TEST_ASSERT(object_id == %(u_cls)s); |
| } |
| """ % dict(root_cls=root.name, u_cls=cls.upper())) |
| |
| if not type_maps.class_is_virtual(cls): |
| out.write(""" |
| if (loci_class_metadata[obj->object_id].wire_length_get != NULL) { |
| int length; |
| |
| loci_class_metadata[obj->object_id].wire_length_get((of_object_t *)obj, &length); |
| TEST_ASSERT(length == %(length)d); |
| } |
| |
| /* Set up incrementing values for scalar members */ |
| %(cls)s_%(v_name)s_populate_scalars(obj, 1); |
| |
| /* Check values just set */ |
| TEST_ASSERT(%(cls)s_%(v_name)s_check_scalars(obj, 1) != 0); |
| """ % dict(cls=cls, u_cls=cls.upper(), |
| v_name=v_name, length=length, version=version)) |
| |
| out.write(""" |
| %(cls)s_delete(obj); |
| |
| /* To do: Check memory */ |
| return TEST_PASS; |
| } |
| """ % dict(cls=cls)) |
| |
| # Get the members and list of scalar types for members of a given class |
| def scalar_member_types_get(cls, version): |
| member_types = [] |
| |
| if not version in of_g.unified[cls]: |
| return ([], []) |
| |
| if "use_version" in of_g.unified[cls][version]: |
| v = of_g.unified[cls][version]["use_version"] |
| members = of_g.unified[cls][v]["members"] |
| else: |
| members = of_g.unified[cls][version]["members"] |
| # Accumulate variables that are supported |
| for member in members: |
| m_type = member["m_type"] |
| m_name = member["name"] |
| if (not loxi_utils.type_is_scalar(m_type) or |
| ignore_member(cls, version, m_name, m_type)): |
| continue |
| if not m_type in member_types: |
| member_types.append(m_type) |
| |
| return (members, member_types) |
| |
| def scalar_funs_instance(out, cls, version, members, member_types): |
| """ |
| Generate one instance of scalar set/check functions |
| """ |
| out.write(""" |
| /** |
| * Populate the scalar values in obj of type %(cls)s, |
| * version %(v_name)s |
| * @param obj Pointer to an object to populate |
| * @param value The seed value to use in populating the object |
| * @returns The value after increments for this object's values |
| */ |
| int %(cls)s_%(v_name)s_populate_scalars( |
| %(cls)s_t *obj, int value) { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| # Declare string types |
| for t in member_types: |
| out.write(" %s %s;\n" % (t, var_name_map(t))) |
| for member in members: |
| m_type = member["m_type"] |
| m_name = member["name"] |
| if (not loxi_utils.type_is_scalar(m_type) or |
| ignore_member(cls, version, m_name, m_type)): |
| continue |
| v_name = var_name_map(m_type); |
| out.write(""" |
| VAR_%(u_type)s_INIT(%(v_name)s, value); |
| %(cls)s_%(m_name)s_set(obj, %(v_name)s); |
| value += 1; |
| """ % dict(cls=cls, m_name=m_name, u_type=m_type.upper(), v_name=v_name)) |
| out.write(""" |
| return value; |
| } |
| """) |
| |
| out.write(""" |
| /** |
| * Check scalar values in obj of type %(cls)s, |
| * version %(v_name)s |
| * @param obj Pointer to an object to check |
| * @param value Starting value for checking |
| * @returns The value after increments for this object's values |
| */ |
| int %(cls)s_%(v_name)s_check_scalars( |
| %(cls)s_t *obj, int value) { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| |
| for t in member_types: |
| out.write(" %s %s;\n" % (t, var_name_map(t))) |
| for member in members: |
| m_type = member["m_type"] |
| m_name = member["name"] |
| if (not loxi_utils.type_is_scalar(m_type) or |
| ignore_member(cls, version, m_name, m_type)): |
| continue |
| v_name = var_name_map(m_type); |
| out.write(""" |
| %(cls)s_%(m_name)s_get(obj, &%(v_name)s); |
| TEST_ASSERT(VAR_%(u_type)s_CHECK(%(v_name)s, value)); |
| value += 1; |
| """ % dict(cls=cls, m_name=m_name, u_type=m_type.upper(), v_name=v_name)) |
| |
| out.write(""" |
| return value; |
| } |
| |
| """) |
| |
| def gen_scalar_set_check_funs(out): |
| """ |
| For each object class with scalar members, generate functions that |
| set and check their values |
| """ |
| for version in of_g.of_version_range: |
| for cls in of_g.standard_class_order: |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| (members, member_types) = scalar_member_types_get(cls, version) |
| scalar_funs_instance(out, cls, version, members, member_types) |
| |
| |
| # Helper function to set up a subclass instance for a test |
| def setup_instance(out, cls, subcls, instance, v_name, version): |
| base_type = loxi_utils.list_to_entry_type(cls) |
| setup_template = """ |
| %(subcls)s_init(%(inst)s, %(v_name)s, -1, 1); |
| %(cls)s_append_bind(list, %(inst)s); |
| value = %(subcls)s_%(v_name)s_populate( |
| %(inst)s, value); |
| cur_len += %(inst)s->length; |
| TEST_ASSERT(list->length == cur_len); |
| """ |
| out.write(""" |
| /* Append two instances of type %s */ |
| """ % subcls) |
| for i in range(2): |
| out.write(setup_template % |
| dict(inst=instance, subcls=subcls, v_name=v_name, |
| base_type=base_type, cls=cls, |
| version=version)) |
| |
| def check_instance(out, cls, subcls, instance, v_name, version, last): |
| check_template = """ |
| TEST_ASSERT(%(inst)s->object_id == %(elt_name)s); |
| value = %(subcls)s_%(v_name)s_check( |
| %(inst)s, value); |
| TEST_ASSERT(value != 0); |
| """ |
| out.write("\n /* Check two instances of type %s */" % instance) |
| |
| out.write(check_template % |
| dict(elt_name=loxi_utils.enum_name(subcls), |
| inst=instance, subcls=subcls, |
| v_name=loxi_utils.version_to_name(version))) |
| out.write("""\ |
| TEST_OK(%(cls)s_next(list, &elt)); |
| """ % dict(cls=cls)) |
| |
| out.write(check_template % |
| dict(elt_name=loxi_utils.enum_name(subcls), |
| inst=instance, subcls=subcls, |
| v_name=loxi_utils.version_to_name(version))) |
| if last: |
| out.write("""\ |
| TEST_ASSERT(%(cls)s_next(list, &elt) == OF_ERROR_RANGE); |
| """ % dict(cls=cls)) |
| else: |
| out.write("""\ |
| TEST_OK(%(cls)s_next(list, &elt)); |
| """ % dict(cls=cls)) |
| |
| def setup_list_fn(out, version, cls): |
| """ |
| Generate a helper function that populates a list with two |
| of each type of subclass it supports |
| """ |
| out.write(""" |
| /** |
| * Set up a list of type %(cls)s with two of each type of subclass |
| */ |
| int |
| list_setup_%(cls)s_%(v_name)s( |
| %(cls)s_t *list, int value) |
| { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| base_type = loxi_utils.list_to_entry_type(cls) |
| out.write(""" |
| of_object_t elt; |
| int cur_len = 0; |
| (void) elt; |
| (void) cur_len; |
| """ % dict(cls=cls, base_type=base_type)) |
| |
| sub_classes = type_maps.sub_class_map(base_type, version) |
| sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)] |
| v_name = loxi_utils.version_to_name(version) |
| |
| if not type_maps.class_is_virtual(base_type): |
| out.write(" /* No subclasses for %s */\n"% base_type) |
| out.write(" %s_t *elt_p;\n" % base_type) |
| out.write("\n elt_p = &elt;\n") |
| else: |
| out.write(" /* Declare pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s_t *%s;\n" % (subcls, instance)) |
| out.write("\n /* Instantiate pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s = &elt;\n" % (instance)) |
| |
| if not type_maps.class_is_virtual(base_type): # No inheritance case |
| setup_instance(out, cls, base_type, "elt_p", v_name, version) |
| else: |
| for instance, subcls in sub_classes: |
| setup_instance(out, cls, subcls, instance, v_name, version) |
| out.write(""" |
| |
| return value; |
| } |
| """) |
| |
| def check_list_fn(out, version, cls): |
| """ |
| Generate a helper function that checks a list populated by above fn |
| """ |
| out.write(""" |
| /** |
| * Check a list of type %(cls)s generated by |
| * list_setup_%(cls)s_%(v_name)s |
| */ |
| int |
| list_check_%(cls)s_%(v_name)s( |
| %(cls)s_t *list, int value) |
| { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| base_type = loxi_utils.list_to_entry_type(cls) |
| out.write(""" |
| of_object_t elt; |
| (void) elt; |
| """ % dict(cls=cls, base_type=base_type)) |
| |
| sub_classes = type_maps.sub_class_map(base_type, version) |
| sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)] |
| v_name = loxi_utils.version_to_name(version) |
| |
| if not type_maps.class_is_virtual(base_type): |
| out.write(" /* No subclasses for %s */\n"% base_type) |
| out.write(" %s_t *elt_p;\n" % base_type) |
| out.write("\n elt_p = &elt;\n") |
| else: |
| out.write(" /* Declare pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s_t *%s;\n" % (subcls, instance)) |
| out.write("\n /* Instantiate pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s = &elt;\n" % (instance)) |
| |
| if not type_maps.class_is_virtual(base_type) or sub_classes: |
| out.write(" TEST_OK(%(cls)s_first(list, &elt));\n" % dict(cls=cls)) |
| |
| if not type_maps.class_is_virtual(base_type): # No inheritance case |
| check_instance(out, cls, base_type, "elt_p", v_name, version, True) |
| else: |
| count = 0 |
| for instance, subcls in sub_classes: |
| count += 1 |
| check_instance(out, cls, subcls, instance, v_name, |
| version, count==len(sub_classes)) |
| |
| out.write(""" |
| return value; |
| } |
| """ % dict(base_type=base_type)) |
| |
| def gen_list_set_check_funs(out): |
| for version in of_g.of_version_range: |
| for cls in of_g.ordered_list_objects: |
| if version in of_g.unified[cls]: |
| setup_list_fn(out, version, cls) |
| check_list_fn(out, version, cls) |
| |
| # Maybe: Get a map from list class to parent, mem_name of container |
| |
| def list_test(out, version, cls): |
| out.write(""" |
| static int |
| test_%(cls)s_%(v_name)s(void) |
| { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| base_type = loxi_utils.list_to_entry_type(cls) |
| |
| out.write(""" %(cls)s_t *list; |
| int value = 1; |
| """ % dict(cls=cls, base_type=base_type)) |
| |
| out.write(""" |
| list = %(cls)s_new(%(v_name)s); |
| TEST_ASSERT(list != NULL); |
| TEST_ASSERT(list->version == %(v_name)s); |
| TEST_ASSERT(list->length == 0); |
| TEST_ASSERT(list->parent == NULL); |
| TEST_ASSERT(list->object_id == %(enum_cls)s); |
| |
| value = list_setup_%(cls)s_%(v_name)s(list, value); |
| TEST_ASSERT(value != 0); |
| """ % dict(cls=cls, base_type=base_type, v_name=loxi_utils.version_to_name(version), |
| enum_cls=loxi_utils.enum_name(cls))) |
| |
| out.write(""" |
| /* Now check values */ |
| value = 1; |
| value = list_check_%(cls)s_%(v_name)s(list, value); |
| TEST_ASSERT(value != 0); |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| |
| out.write(""" |
| %(cls)s_delete(list); |
| |
| return TEST_PASS; |
| } |
| """ % dict(cls=cls)) |
| |
| def gen_list_test(out, name): |
| """ |
| Generate base line test cases for lists |
| @param out The file handle to write to |
| """ |
| |
| loxi_utils.gen_c_copy_license(out) |
| out.write(""" |
| /** |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| * |
| * Message-scalar tests for all versions |
| */ |
| |
| #include <locitest/test_common.h> |
| """) |
| |
| for version in of_g.of_version_range: |
| v_name = loxi_utils.version_to_name(version) |
| out.write(""" |
| /** |
| * Baseline list tests for version %s |
| */ |
| """ % v_name) |
| for cls in of_g.ordered_list_objects: |
| if version in of_g.unified[cls]: |
| list_test(out, version, cls) |
| |
| out.write(""" |
| int |
| run_list_tests(void) |
| { |
| """) |
| for version in of_g.of_version_range: |
| v_name = loxi_utils.version_to_name(version) |
| for cls in of_g.ordered_list_objects: |
| if version in of_g.unified[cls]: |
| test_name = "%s_%s" % (cls, v_name) |
| out.write(" RUN_TEST(%s);\n" % test_name) |
| |
| out.write("\n return TEST_PASS;\n}\n"); |
| |
| def gen_match_test(out, name): |
| """ |
| Generate baseline tests for match functions |
| """ |
| |
| loxi_utils.gen_c_copy_license(out) |
| out.write("""\ |
| /** |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| * |
| * Message-scalar tests for all versions |
| * @fixme These are mostly hard coded now. |
| */ |
| |
| #include <locitest/test_common.h> |
| |
| static int |
| test_match_1(void) |
| { |
| """) |
| |
| for version in of_g.of_version_range: |
| out.write(" of_match_v%(v)d_t *m_v%(v)d;\n" % dict(v=version)) |
| |
| out.write("""\ |
| of_match_t match; |
| int value = 1; |
| int idx; |
| uint32_t exp_value; |
| |
| /* Verify default values for ip mask map */ |
| for (idx = 0; idx < 64; idx++) { |
| exp_value = (idx < 32) ? ~((1 << idx) - 1) : 0; |
| TEST_ASSERT(of_ip_index_to_mask(idx) == exp_value); |
| if (idx < 32) { |
| TEST_ASSERT(of_ip_mask_to_index(exp_value) == idx); |
| } |
| } |
| """) |
| |
| for version in of_g.of_version_range: |
| out.write(""" |
| /* Create/populate/convert and delete for version %(v_name)s */ |
| m_v%(version)d = of_match_v%(version)d_new(%(v_name)s); |
| TEST_ASSERT(m_v%(version)d != NULL); |
| TEST_ASSERT((value = of_match_populate(&match, %(v_name)s, value)) > 0); |
| TEST_OK(of_match_to_wire_match_v%(version)d(&match, m_v%(version)d)); |
| of_match_v%(version)d_delete(m_v%(version)d); |
| """ % dict(v_name=loxi_utils.version_to_name(version), version=version)) |
| |
| out.write(""" |
| return TEST_PASS; |
| } |
| """) |
| |
| out.write(""" |
| static int |
| test_match_2(void) |
| { |
| """) |
| |
| for version in of_g.of_version_range: |
| out.write(" of_match_v%(v)d_t *m_v%(v)d;\n" % dict(v=version)) |
| |
| out.write("""\ |
| of_match_t match1; |
| of_match_t match2; |
| int value = 1; |
| """) |
| |
| for version in of_g.of_version_range: |
| out.write(""" |
| TEST_ASSERT((value = of_match_populate(&match1, %(v_name)s, value)) > 0); |
| m_v%(version)d = of_match_v%(version)d_new(%(v_name)s); |
| TEST_ASSERT(m_v%(version)d != NULL); |
| TEST_OK(of_match_to_wire_match_v%(version)d(&match1, m_v%(version)d)); |
| TEST_OK(of_match_v%(version)d_to_match(m_v%(version)d, &match2)); |
| TEST_ASSERT(memcmp(&match1, &match2, sizeof(match1)) == 0); |
| of_match_v%(version)d_delete(m_v%(version)d); |
| """ % dict(v_name=loxi_utils.version_to_name(version), version=version)) |
| |
| out.write(""" |
| return TEST_PASS; |
| } |
| """) |
| |
| out.write(""" |
| static int |
| test_match_3(void) |
| { |
| of_match_t match1; |
| of_match_t match2; |
| int value = 1; |
| of_octets_t octets; |
| of_object_storage_t storage; |
| memset(&storage, 0, sizeof(storage)); |
| storage.obj.wbuf = &storage.wbuf; |
| """) |
| for version in of_g.of_version_range: |
| out.write(""" |
| /* Serialize to version %(v_name)s */ |
| TEST_ASSERT((value = of_match_populate(&match1, %(v_name)s, value)) > 0); |
| TEST_ASSERT(of_match_serialize(%(v_name)s, &match1, &octets) == |
| OF_ERROR_NONE); |
| storage.obj.wbuf->buf = octets.data; |
| storage.obj.wbuf->alloc_bytes = octets.bytes; |
| storage.obj.wbuf->current_bytes = octets.bytes; |
| TEST_ASSERT(of_match_deserialize(%(v_name)s, &match2, &storage.obj, 0, octets.bytes) == |
| OF_ERROR_NONE); |
| TEST_ASSERT(memcmp(&match1, &match2, sizeof(match1)) == 0); |
| FREE(octets.data); |
| """ % dict(v_name=loxi_utils.version_to_name(version), version=version)) |
| |
| out.write(""" |
| return TEST_PASS; |
| } |
| """) |
| |
| out.write(""" |
| int run_match_tests(void) |
| { |
| RUN_TEST(match_1); |
| RUN_TEST(match_2); |
| RUN_TEST(match_3); |
| RUN_TEST(match_utils); |
| |
| return TEST_PASS; |
| } |
| """) |
| |
| def gen_msg_test(out, name): |
| loxi_utils.gen_c_copy_license(out) |
| out.write(""" |
| /** |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| * |
| * Message-scalar tests for all versions |
| */ |
| |
| #include <locitest/test_common.h> |
| """) |
| for version in of_g.of_version_range: |
| for cls in of_g.ordered_messages: |
| if not (cls, version) in of_g.base_length: |
| continue |
| if type_maps.class_is_virtual(cls): |
| continue |
| bytes = of_g.base_length[(cls, version)] |
| out.write(""" |
| static int |
| test_%(cls)s_create_%(v_name)s(void) |
| { |
| %(cls)s_t *obj; |
| uint8_t *msg_buf; |
| int value; |
| of_object_id_t object_id; |
| int len; |
| |
| obj = %(cls)s_new(%(v_name)s); |
| TEST_ASSERT(obj != NULL); |
| TEST_ASSERT(obj->version == %(v_name)s); |
| TEST_ASSERT(obj->length == %(bytes)d); |
| TEST_ASSERT(obj->parent == NULL); |
| TEST_ASSERT(obj->object_id == %(enum)s); |
| |
| of_header_wire_object_id_get(obj, &object_id); |
| TEST_ASSERT(object_id == %(enum)s); |
| |
| /* Set up incrementing values for scalar members */ |
| value = %(cls)s_%(v_name)s_populate_scalars(obj, 1); |
| TEST_ASSERT(value != 0); |
| |
| len = obj->length; |
| |
| /* Grab the underlying buffer from the message */ |
| of_object_wire_buffer_steal((of_object_t *)obj, &msg_buf); |
| TEST_ASSERT(msg_buf != NULL); |
| %(cls)s_delete(obj); |
| obj = of_object_new_from_message(OF_BUFFER_TO_MESSAGE(msg_buf), len); |
| |
| TEST_ASSERT(obj != NULL); |
| |
| /* @fixme Set up all message objects (recursively?) */ |
| |
| value = %(cls)s_%(v_name)s_check_scalars(obj, 1); |
| TEST_ASSERT(value != 0); |
| |
| %(cls)s_delete(obj); |
| |
| return TEST_PASS; |
| } |
| """ % dict(cls=cls, version=version, enum=loxi_utils.enum_name(cls), |
| v_name=loxi_utils.version_to_name(version), bytes=bytes)) |
| |
| out.write(""" |
| int |
| run_message_tests(void) |
| { |
| """) |
| for version in of_g.of_version_range: |
| for cls in of_g.ordered_messages: |
| if not (cls, version) in of_g.base_length: |
| continue |
| if type_maps.class_is_virtual(cls): |
| continue |
| test_name = "%s_create_%s" % (cls, loxi_utils.version_to_name(version)) |
| out.write(" RUN_TEST(%s);\n" % test_name) |
| |
| out.write("\n return TEST_PASS;\n}\n"); |
| |
| |
| def gen_list_setup_check(out, cls, version): |
| """ |
| Generate functions that populate and check a list with two |
| of each type of subclass it supports |
| """ |
| out.write(""" |
| /** |
| * Populate a list of type %(cls)s with two of each type of subclass |
| * @param list Pointer to the list to be populated |
| * @param value The seed value to use in populating the list |
| * @returns The value after increments for this object's values |
| */ |
| int |
| %(cls)s_%(v_name)s_populate( |
| %(cls)s_t *list, int value) |
| { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| base_type = loxi_utils.list_to_entry_type(cls) |
| out.write(""" |
| of_object_t elt; |
| int cur_len = 0; |
| (void) elt; |
| (void) cur_len; |
| """ % dict(cls=cls, base_type=base_type)) |
| |
| sub_classes = type_maps.sub_class_map(base_type, version) |
| sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)] |
| v_name = loxi_utils.version_to_name(version) |
| |
| if not type_maps.class_is_virtual(base_type): |
| out.write(" /* No subclasses for %s */\n"% base_type) |
| out.write(" %s_t *elt_p;\n" % base_type) |
| out.write("\n elt_p = &elt;\n") |
| else: |
| out.write(" /* Declare pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s_t *%s;\n" % (subcls, instance)) |
| out.write("\n /* Instantiate pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s = &elt;\n" % (instance)) |
| |
| if not type_maps.class_is_virtual(base_type): # No inheritance case |
| setup_instance(out, cls, base_type, "elt_p", v_name, version) |
| else: |
| for instance, subcls in sub_classes: |
| setup_instance(out, cls, subcls, instance, v_name, version) |
| out.write(""" |
| return value; |
| } |
| """) |
| out.write(""" |
| /** |
| * Check a list of type %(cls)s generated by |
| * %(cls)s_%(v_name)s_populate |
| * @param list Pointer to the list that was populated |
| * @param value Starting value for checking |
| * @returns The value after increments for this object's values |
| */ |
| int |
| %(cls)s_%(v_name)s_check( |
| %(cls)s_t *list, int value) |
| { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| base_type = loxi_utils.list_to_entry_type(cls) |
| out.write(""" |
| of_object_t elt; |
| int count = 0; |
| int rv; |
| """ % dict(cls=cls, base_type=base_type)) |
| |
| |
| sub_classes = type_maps.sub_class_map(base_type, version) |
| sub_classes = [(instance, subcls) for (instance, subcls) in sub_classes if not type_maps.class_is_virtual(subcls)] |
| v_name = loxi_utils.version_to_name(version) |
| |
| if not type_maps.class_is_virtual(base_type): |
| entry_count = 2 |
| out.write(" /* No subclasses for %s */\n"% base_type) |
| out.write(" %s_t *elt_p;\n" % base_type) |
| out.write("\n elt_p = &elt;\n") |
| else: |
| entry_count = 2 * len(sub_classes) # Two of each type appended |
| out.write(" /* Declare pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s_t *%s;\n" % (subcls, instance)) |
| out.write("\n /* Instantiate pointers for each subclass */\n") |
| for instance, subcls in sub_classes: |
| out.write(" %s = &elt;\n" % (instance)) |
| |
| if not type_maps.class_is_virtual(base_type) or sub_classes: |
| out.write(" TEST_OK(%(cls)s_first(list, &elt));\n" % dict(cls=cls)) |
| |
| if not type_maps.class_is_virtual(base_type): # No inheritance case |
| check_instance(out, cls, base_type, "elt_p", v_name, |
| version, True) |
| else: |
| count = 0 |
| for instance, subcls in sub_classes: |
| count += 1 |
| check_instance(out, cls, subcls, instance, v_name, |
| version, count==len(sub_classes)) |
| out.write(""" |
| """ % dict(base_type=base_type)) |
| |
| out.write(""" |
| /* Do an iterate to test the iterator */ |
| %(u_cls)s_ITER(list, &elt, rv) { |
| count += 1; |
| } |
| |
| TEST_ASSERT(rv == OF_ERROR_RANGE); |
| TEST_ASSERT(count == %(entry_count)d); |
| |
| /* We shoehorn a test of the dup functions here */ |
| { |
| %(cls)s_t *dup; |
| |
| TEST_ASSERT((dup = %(cls)s_dup(list)) != NULL); |
| TEST_ASSERT(dup->length == list->length); |
| TEST_ASSERT(dup->object_id == list->object_id); |
| TEST_ASSERT(dup->version == list->version); |
| TEST_ASSERT(MEMCMP(OF_OBJECT_BUFFER_INDEX(dup, 0), |
| OF_OBJECT_BUFFER_INDEX(list, 0), list->length) == 0); |
| of_object_delete((of_object_t *)dup); |
| |
| /* And now for the generic dup function */ |
| TEST_ASSERT((dup = (%(cls)s_t *) |
| of_object_dup(list)) != NULL); |
| TEST_ASSERT(dup->length == list->length); |
| TEST_ASSERT(dup->object_id == list->object_id); |
| TEST_ASSERT(dup->version == list->version); |
| TEST_ASSERT(MEMCMP(OF_OBJECT_BUFFER_INDEX(dup, 0), |
| OF_OBJECT_BUFFER_INDEX(list, 0), list->length) == 0); |
| of_object_delete((of_object_t *)dup); |
| } |
| |
| return value; |
| } |
| """ % dict(cls=cls, u_cls=cls.upper(), entry_count=entry_count)) |
| |
| |
| def gen_class_setup_check(out, cls, version): |
| out.write(""" |
| /** |
| * Populate all members of an object of type %(cls)s |
| * with incrementing values |
| * @param obj Pointer to an object to populate |
| * @param value The seed value to use in populating the object |
| * @returns The value after increments for this object's values |
| */ |
| |
| int |
| %(cls)s_%(v_name)s_populate( |
| %(cls)s_t *obj, int value) |
| { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| members, member_types = loxi_utils.all_member_types_get(cls, version) |
| for m_type in member_types: |
| if loxi_utils.type_is_scalar(m_type) or m_type in ["of_match_t", "of_octets_t"]: |
| out.write(" %s %s;\n" % (m_type, var_name_map(m_type))) |
| elif m_type in embedded_subclasses: |
| subcls = embedded_subclasses[m_type] |
| out.write(" %s_t *%s;\n" % (subcls, var_name_map(m_type))) |
| else: |
| out.write(" %s *%s;\n" % (m_type, var_name_map(m_type))) |
| out.write(""" |
| /* Run thru accessors after new to ensure okay */ |
| """) |
| for member in members: |
| m_type = member["m_type"] |
| m_name = member["name"] |
| if loxi_utils.skip_member_name(m_name): |
| continue |
| if m_type in embedded_subclasses: |
| continue |
| if loxi_utils.type_is_scalar(m_type) or m_type in ["of_match_t", "of_octets_t"]: |
| out.write("""\ |
| %(cls)s_%(m_name)s_get(obj, &%(var_name)s); |
| """ % dict(var_name=var_name_map(m_type), cls=cls, m_name=m_name)) |
| else: |
| sub_cls = m_type[:-2] # Trim _t |
| out.write("""\ |
| { |
| %(sub_cls)s_t sub_cls; |
| |
| /* Test bind */ |
| %(cls)s_%(m_name)s_bind(obj, &sub_cls); |
| } |
| """ % dict(var_name=var_name_map(m_type), cls=cls, |
| m_name=m_name, sub_cls=sub_cls, |
| v_name=loxi_utils.version_to_name(version))) |
| |
| out.write(""" |
| value = %(cls)s_%(v_name)s_populate_scalars( |
| obj, value); |
| TEST_ASSERT(value != 0); |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| |
| for member in members: |
| m_type = member["m_type"] |
| m_name = member["name"] |
| if loxi_utils.type_is_scalar(m_type): # Handled by call to scalar setup |
| continue |
| if loxi_utils.skip_member_name(m_name): |
| continue |
| if m_type == "of_match_t": |
| out.write("""\ |
| value = of_match_populate(&%(var_name)s, %(v_name)s, value); |
| TEST_ASSERT(value != 0); |
| %(cls)s_%(m_name)s_set( |
| obj, &%(var_name)s); |
| """ % dict(cls=cls, var_name=var_name_map(m_type), |
| m_name=m_name, v_name=loxi_utils.version_to_name(version))) |
| elif m_type == "of_octets_t": |
| out.write("""\ |
| value = of_octets_populate(&%(var_name)s, value); |
| TEST_ASSERT(value != 0); |
| %(cls)s_%(m_name)s_set( |
| obj, &%(var_name)s); |
| if (octets.bytes) { |
| FREE(octets.data); |
| } |
| """ % dict(var_name=var_name_map(m_type), cls=cls, m_name=m_name)) |
| elif m_type in embedded_subclasses: |
| sub_cls = embedded_subclasses[m_type] |
| out.write("""\ |
| %(var_name)s = %(sub_cls)s_new(%(v_name)s); |
| TEST_ASSERT(%(var_name)s != NULL); |
| value = %(sub_cls)s_%(v_name)s_populate( |
| %(var_name)s, value); |
| TEST_ASSERT(value != 0); |
| %(cls)s_%(m_name)s_set( |
| obj, %(var_name)s); |
| %(sub_cls)s_delete(%(var_name)s); |
| """ % dict(cls=cls, sub_cls=sub_cls, m_name=m_name, m_type=m_type, |
| var_name=var_name_map(m_type), |
| v_name=loxi_utils.version_to_name(version))) |
| else: |
| sub_cls = m_type[:-2] # Trim _t |
| out.write(""" |
| %(var_name)s = %(sub_cls)s_new(%(v_name)s); |
| TEST_ASSERT(%(var_name)s != NULL); |
| value = %(sub_cls)s_%(v_name)s_populate( |
| %(var_name)s, value); |
| TEST_ASSERT(value != 0); |
| %(cls)s_%(m_name)s_set( |
| obj, %(var_name)s); |
| %(sub_cls)s_delete(%(var_name)s); |
| """ % dict(cls=cls, sub_cls=sub_cls, m_name=m_name, m_type=m_type, |
| var_name=var_name_map(m_type), |
| v_name=loxi_utils.version_to_name(version))) |
| |
| out.write(""" |
| return value; |
| } |
| """) |
| |
| out.write(""" |
| /** |
| * Check all members of an object of type %(cls)s |
| * populated by the above function |
| * @param obj Pointer to an object to check |
| * @param value Starting value for checking |
| * @returns The value after increments for this object's values |
| */ |
| |
| int |
| %(cls)s_%(v_name)s_check( |
| %(cls)s_t *obj, int value) |
| { |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| members, member_types = loxi_utils.all_member_types_get(cls, version) |
| for m_type in member_types: |
| if loxi_utils.type_is_scalar(m_type): # Handled by call to scalar setup |
| continue |
| if loxi_utils.type_is_of_object(m_type): |
| continue |
| out.write(" %s %s;\n" % (m_type, var_name_map(m_type))) |
| out.write(""" |
| value = %(cls)s_%(v_name)s_check_scalars( |
| obj, value); |
| TEST_ASSERT(value != 0); |
| """ % dict(cls=cls, v_name=loxi_utils.version_to_name(version))) |
| |
| for member in members: |
| m_type = member["m_type"] |
| m_name = member["name"] |
| if loxi_utils.type_is_scalar(m_type): # Handled by call to scalar setup |
| continue |
| if loxi_utils.skip_member_name(m_name): |
| continue |
| if m_type == "of_match_t": |
| out.write("""\ |
| %(cls)s_%(m_name)s_get(obj, &%(var_name)s); |
| value = of_match_check(&%(var_name)s, %(v_name)s, value); |
| """ % dict(cls=cls, var_name=var_name_map(m_type), m_name=m_name, |
| v_name=loxi_utils.version_to_name(version))) |
| elif m_type == "of_octets_t": |
| out.write("""\ |
| %(cls)s_%(m_name)s_get(obj, &%(var_name)s); |
| value = of_octets_check(&%(var_name)s, value); |
| """ % dict(cls=cls, var_name=var_name_map(m_type), m_name=m_name, |
| v_name=loxi_utils.version_to_name(version))) |
| elif m_type in embedded_subclasses: |
| sub_cls = embedded_subclasses[m_type] |
| out.write(""" |
| { /* Use get/delete to access on check */ |
| %(sub_cls)s_t *%(m_name)s_ptr; |
| |
| %(m_name)s_ptr = %(cls)s_%(m_name)s_get(obj); |
| TEST_ASSERT(%(m_name)s_ptr != NULL); |
| value = %(sub_cls)s_%(v_name)s_check( |
| %(m_name)s_ptr, value); |
| TEST_ASSERT(value != 0); |
| %(sub_cls)s_delete(%(m_name)s_ptr); |
| } |
| """ % dict(cls=cls, sub_cls=sub_cls, m_name=m_name, m_type=m_type, |
| var_name=var_name_map(m_type), |
| v_name=loxi_utils.version_to_name(version))) |
| else: |
| sub_cls = m_type[:-2] # Trim _t |
| out.write(""" |
| { /* Use get/delete to access on check */ |
| %(m_type)s *%(m_name)s_ptr; |
| |
| %(m_name)s_ptr = %(cls)s_%(m_name)s_get(obj); |
| TEST_ASSERT(%(m_name)s_ptr != NULL); |
| value = %(sub_cls)s_%(v_name)s_check( |
| %(m_name)s_ptr, value); |
| TEST_ASSERT(value != 0); |
| %(sub_cls)s_delete(%(m_name)s_ptr); |
| } |
| """ % dict(cls=cls, sub_cls=sub_cls, m_name=m_name, m_type=m_type, |
| var_name=var_name_map(m_type), |
| v_name=loxi_utils.version_to_name(version))) |
| |
| out.write(""" |
| /* We shoehorn a test of the dup functions here */ |
| { |
| %(cls)s_t *dup; |
| |
| TEST_ASSERT((dup = %(cls)s_dup(obj)) != NULL); |
| TEST_ASSERT(dup->length == obj->length); |
| TEST_ASSERT(dup->object_id == obj->object_id); |
| TEST_ASSERT(dup->version == obj->version); |
| TEST_ASSERT(MEMCMP(OF_OBJECT_BUFFER_INDEX(dup, 0), |
| OF_OBJECT_BUFFER_INDEX(obj, 0), obj->length) == 0); |
| of_object_delete((of_object_t *)dup); |
| |
| /* And now for the generic dup function */ |
| TEST_ASSERT((dup = (%(cls)s_t *) |
| of_object_dup(obj)) != NULL); |
| TEST_ASSERT(dup->length == obj->length); |
| TEST_ASSERT(dup->object_id == obj->object_id); |
| TEST_ASSERT(dup->version == obj->version); |
| TEST_ASSERT(MEMCMP(OF_OBJECT_BUFFER_INDEX(dup, 0), |
| OF_OBJECT_BUFFER_INDEX(obj, 0), obj->length) == 0); |
| of_object_delete((of_object_t *)dup); |
| } |
| |
| return value; |
| } |
| """ % dict(cls=cls)) |
| |
| def unified_accessor_test_case(out, cls, version): |
| """ |
| Generate one test case for the given version and class |
| """ |
| |
| members, member_types = scalar_member_types_get(cls, version) |
| length = of_g.base_length[(cls, version)] |
| v_name = loxi_utils.version_to_name(version) |
| |
| out.write(""" |
| static int |
| test_%(cls)s_%(v_name)s(void) |
| { |
| %(cls)s_t *obj; |
| obj = %(cls)s_new(%(v_name)s); |
| TEST_ASSERT(obj != NULL); |
| TEST_ASSERT(obj->version == %(v_name)s); |
| TEST_ASSERT(obj->length == %(length)d); |
| TEST_ASSERT(obj->parent == NULL); |
| TEST_ASSERT(obj->object_id == %(u_cls)s); |
| """ % dict(cls=cls, u_cls=cls.upper(), |
| v_name=v_name, length=length, version=version)) |
| if (not type_maps.class_is_virtual(cls)) or loxi_utils.class_is_list(cls): |
| out.write(""" |
| if (loci_class_metadata[obj->object_id].wire_length_get != NULL) { |
| int length; |
| |
| loci_class_metadata[obj->object_id].wire_length_get((of_object_t *)obj, &length); |
| TEST_ASSERT(length == %(length)d); |
| } |
| if (loci_class_metadata[obj->object_id].wire_type_get != NULL) { |
| of_object_id_t obj_id; |
| |
| loci_class_metadata[obj->object_id].wire_type_get((of_object_t *)obj, &obj_id); |
| TEST_ASSERT(obj_id == %(u_cls)s); |
| } |
| |
| /* Set up incrementing values for members */ |
| TEST_ASSERT(%(cls)s_%(v_name)s_populate( |
| obj, 1) != 0); |
| |
| /* Check values just set */ |
| TEST_ASSERT(%(cls)s_%(v_name)s_check( |
| obj, 1) != 0); |
| """ % dict(cls=cls, u_cls=cls.upper(), |
| v_name=v_name, length=length, version=version)) |
| |
| out.write(""" |
| %(cls)s_delete(obj); |
| |
| /* To do: Check memory */ |
| return TEST_PASS; |
| } |
| """ % dict(cls=cls)) |
| |
| |
| def gen_unified_accessor_funs(out): |
| for version in of_g.of_version_range: |
| for cls in of_g.standard_class_order: |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| elif loxi_utils.class_is_list(cls): |
| gen_list_setup_check(out, cls, version) |
| else: |
| gen_class_setup_check(out, cls, version) |
| |
| def gen_unified_accessor_tests(out, name): |
| loxi_utils.gen_c_copy_license(out) |
| out.write(""" |
| /** |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| * |
| * Unified simple class instantiation tests for all versions |
| */ |
| |
| #include <locitest/test_common.h> |
| """) |
| for version in of_g.of_version_range: |
| for cls in of_g.standard_class_order: |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| unified_accessor_test_case(out, cls, version) |
| |
| out.write(""" |
| int |
| run_unified_accessor_tests(void) |
| { |
| """) |
| for version in of_g.of_version_range: |
| v_name = loxi_utils.version_to_name(version) |
| for cls in of_g.standard_class_order: |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| test_name = "%s_%s" % (cls, v_name) |
| out.write(" RUN_TEST(%s);\n" % test_name) |
| |
| out.write(" return TEST_PASS;\n}\n"); |
| |
| |
| |
| ################################################################ |
| # |
| # Object duplication functions |
| # |
| # These exercise the accessors to create duplicate objects. |
| # They are used in the LOCI test shim which sits in an OF |
| # protocol stream. |
| # |
| # TODO |
| # Resolve version stuff |
| # Complete list dup |
| |
| def gen_dup_list(out, cls, version): |
| ver_name = loxi_utils.version_to_name(version) |
| elt_type = loxi_utils.list_to_entry_type(cls) |
| out.write(""" |
| /** |
| * Duplicate a list of type %(cls)s |
| * using accessor functions |
| * @param src Pointer to object to be duplicated |
| * @returns A new object of type %(cls)s. |
| * |
| * The caller is responsible for deleting the returned value |
| */ |
| %(cls)s_t * |
| %(cls)s_%(ver_name)s_dup( |
| %(cls)s_t *src) |
| { |
| of_object_t src_elt; |
| of_object_t *dst_elt; |
| int rv; |
| %(cls)s_t *dst; |
| |
| if ((dst = %(cls)s_new(src->version)) == NULL) { |
| return NULL; |
| } |
| """ % dict(elt_type=elt_type, cls=cls, ver_name=ver_name)) |
| |
| out.write(""" |
| %(u_cls)s_ITER(src, &src_elt, rv) { |
| if ((dst_elt = %(elt_type)s_%(ver_name)s_dup(&src_elt)) == NULL) { |
| of_object_delete((of_object_t *)dst); |
| return NULL; |
| } |
| _TRY_FREE(%(cls)s_append(dst, dst_elt), |
| dst, NULL); |
| of_object_delete((of_object_t *)dst_elt); |
| } |
| |
| return dst; |
| } |
| """ % dict(u_cls=cls.upper(), elt_type=elt_type, cls=cls, ver_name=ver_name)) |
| |
| |
| def gen_dup_inheritance(out, cls, version): |
| ver_name = loxi_utils.version_to_name(version) |
| out.write(""" |
| /** |
| * Duplicate a super class object of type %(cls)s |
| * @param src Pointer to object to be duplicated |
| * @returns A new object of type %(cls)s. |
| * |
| * The caller is responsible for deleting the returned value |
| */ |
| of_object_t * |
| %(cls)s_%(ver_name)s_dup( |
| of_object_t *src) |
| { |
| """ % dict(cls=cls, ver_name=ver_name)) |
| |
| # For each subclass, check if this is an instance of that subclass |
| sub_classes = type_maps.sub_class_map(cls, version) |
| for (_, sub_cls) in sub_classes: |
| sub_enum = sub_cls.upper() |
| out.write(""" |
| if (src->object_id == %(sub_enum)s) { |
| return %(sub_cls)s_%(ver_name)s_dup(src); |
| } |
| """ % dict(sub_cls=sub_cls, ver_name=ver_name, sub_enum=sub_enum, cls=cls)) |
| |
| out.write(""" |
| return NULL; |
| } |
| """) |
| |
| |
| def gen_dup_cls(out, cls, version): |
| """ |
| Generate duplication routine for class cls |
| """ |
| ver_name = loxi_utils.version_to_name(version) |
| |
| out.write(""" |
| /** |
| * Duplicate an object of type %(cls)s |
| * using accessor functions |
| * @param src Pointer to object to be duplicated |
| * @returns A new object of type %(cls)s. |
| * |
| * The caller is responsible for deleting the returned value |
| */ |
| %(cls)s_t * |
| %(cls)s_%(ver_name)s_dup( |
| %(cls)s_t *src) |
| { |
| %(cls)s_t *dst; |
| """ % dict(cls=cls, ver_name=ver_name)) |
| |
| # Get members and types for the class |
| members, member_types = loxi_utils.all_member_types_get(cls, version) |
| |
| # Add declarations for each member type |
| for m_type in member_types: |
| if loxi_utils.type_is_scalar(m_type) or m_type in ["of_match_t", "of_octets_t"]: |
| # Declare instance of these |
| out.write(" %s %s;\n" % (m_type, var_name_map(m_type))) |
| elif m_type in embedded_subclasses: |
| sub_cls = embedded_subclasses[m_type] |
| out.write(""" |
| %(sub_cls)s_t src_%(v_name)s; |
| %(sub_cls)s_t *dst_%(v_name)s; |
| """ % dict(v_name=var_name_map(m_type), sub_cls=sub_cls)) |
| else: |
| out.write(""" |
| %(m_type)s src_%(v_name)s; |
| %(m_type)s *dst_%(v_name)s; |
| """ % dict(m_type=m_type, v_name=var_name_map(m_type))) |
| |
| out.write(""" |
| if ((dst = %(cls)s_new(src->version)) == NULL) { |
| return NULL; |
| } |
| """ % dict(cls=cls)) |
| |
| for member in members: |
| m_type = member["m_type"] |
| m_name = member["name"] |
| if loxi_utils.skip_member_name(m_name): |
| continue |
| if loxi_utils.type_is_scalar(m_type): # Handled by call to scalar setup |
| out.write(""" |
| %(cls)s_%(m_name)s_get(src, &%(v_name)s); |
| %(cls)s_%(m_name)s_set(dst, %(v_name)s); |
| """ % dict(cls=cls, m_name=m_name, v_name=var_name_map(m_type))) |
| elif m_type in ["of_match_t", "of_octets_t"]: |
| out.write(""" |
| %(cls)s_%(m_name)s_get(src, &%(v_name)s); |
| %(cls)s_%(m_name)s_set(dst, &%(v_name)s); |
| """ % dict(cls=cls, m_name=m_name, v_name=var_name_map(m_type))) |
| elif m_type in embedded_subclasses: |
| sub_cls = embedded_subclasses[m_type] |
| out.write(""" |
| %(cls)s_%(m_name)s_bind( |
| src, &src_%(v_name)s); |
| dst_%(v_name)s = %(sub_cls)s_%(ver_name)s_dup(&src_%(v_name)s); |
| if (dst_%(v_name)s == NULL) { |
| %(cls)s_delete(dst); |
| return NULL; |
| } |
| %(cls)s_%(m_name)s_set(dst, dst_%(v_name)s); |
| %(sub_cls)s_delete(dst_%(v_name)s); |
| """ % dict(sub_cls=sub_cls, cls=cls, m_name=m_name, |
| v_name=var_name_map(m_type), ver_name=ver_name)) |
| else: |
| sub_cls = m_type[:-2] # Trim _t |
| out.write(""" |
| %(cls)s_%(m_name)s_bind( |
| src, &src_%(v_name)s); |
| dst_%(v_name)s = %(sub_cls)s_%(ver_name)s_dup(&src_%(v_name)s); |
| if (dst_%(v_name)s == NULL) { |
| %(cls)s_delete(dst); |
| return NULL; |
| } |
| %(cls)s_%(m_name)s_set(dst, dst_%(v_name)s); |
| %(sub_cls)s_delete(dst_%(v_name)s); |
| """ % dict(sub_cls=sub_cls, cls=cls, m_name=m_name, |
| v_name=var_name_map(m_type), ver_name=ver_name)) |
| |
| out.write(""" |
| return dst; |
| } |
| """) |
| |
| def gen_version_dup(out=sys.stdout): |
| """ |
| Generate duplication routines for each object type |
| """ |
| out.write(""" |
| /* Special try macro for duplicating */ |
| #define _TRY_FREE(op, obj, rv) do { \\ |
| int _rv; \\ |
| if ((_rv = (op)) < 0) { \\ |
| LOCI_LOG_ERROR("ERROR %d at %s:%d\\n", _rv, __FILE__, __LINE__); \\ |
| of_object_delete((of_object_t *)(obj)); \\ |
| return (rv); \\ |
| } \\ |
| } while (0) |
| """) |
| |
| for version in of_g.of_version_range: |
| for cls in of_g.standard_class_order: |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| if type_maps.class_is_inheritance_root(cls): |
| gen_dup_inheritance(out, cls, version) |
| elif loxi_utils.class_is_list(cls): |
| gen_dup_list(out, cls, version) |
| else: |
| gen_dup_cls(out, cls, version) |
| |
| def gen_dup(out=sys.stdout): |
| """ |
| Generate non-version specific duplication routines for each object type |
| """ |
| |
| for cls in of_g.standard_class_order: |
| out.write(""" |
| of_object_t * |
| %(cls)s_dup( |
| of_object_t *src) |
| { |
| """ % dict(cls=cls)) |
| for version in of_g.of_version_range: |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| |
| ver_name = loxi_utils.version_to_name(version) |
| out.write(""" |
| if (src->version == %(ver_name)s) { |
| return %(cls)s_%(ver_name)s_dup(src); |
| } |
| """ % dict(cls=cls, ver_name=ver_name)) |
| |
| out.write(""" |
| /* Class not supported in given version */ |
| return NULL; |
| } |
| """) |
| |
| def dup_c_gen(out, name): |
| """ |
| Generate the C file for duplication functions |
| """ |
| loxi_utils.gen_c_copy_license(out) |
| out.write("""\ |
| /* |
| * Duplication functions for all OF objects |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| * |
| * These are test functions for exercising accessors. You can call |
| * of_object_dup for an efficient duplication. |
| */ |
| |
| #define DISABLE_WARN_UNUSED_RESULT |
| #include "loci_log.h" |
| #include <locitest/of_dup.h> |
| |
| """) |
| |
| gen_version_dup(out) |
| gen_dup(out) |
| |
| |
| def dup_h_gen(out, name): |
| """ |
| Generate the header file for duplication functions |
| """ |
| |
| loxi_utils.gen_c_copy_license(out) |
| out.write(""" |
| /* |
| * Duplication function header file |
| * |
| * AUTOMATICALLY GENERATED FILE. Edits will be lost on regen. |
| */ |
| |
| #if !defined(_OF_DUP_H_) |
| #define _OF_DUP_H_ |
| |
| #include <loci/loci.h> |
| """) |
| |
| for cls in of_g.standard_class_order: |
| out.write(""" |
| extern of_object_t * |
| %(cls)s_dup( |
| of_object_t *src); |
| """ % dict(cls=cls)) |
| |
| for version in of_g.of_version_range: |
| for cls in of_g.standard_class_order: |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| ver_name = loxi_utils.version_to_name(version) |
| out.write(""" |
| extern of_object_t * |
| %(cls)s_%(ver_name)s_dup( |
| of_object_t *src); |
| """ % dict(cls=cls, ver_name=ver_name)) |
| |
| out.write("\n#endif /* _OF_DUP_H_ */\n") |
| |
| def gen_log_test(out): |
| """ |
| Generate test for obj log calls |
| |
| Define a trivial handler for object logging; call all obj log fns |
| """ |
| out.write(""" |
| |
| /** |
| * Test object dump functions |
| */ |
| |
| int |
| test_dump_objs(void) |
| { |
| of_object_t *obj; |
| |
| FILE *out = fopen("/dev/null", "w"); |
| |
| /* Call each obj dump function */ |
| """) |
| for version in of_g.of_version_range: |
| for j, cls in enumerate(of_g.all_class_order): |
| if not loxi_utils.class_in_version(cls, version): |
| continue |
| if type_maps.class_is_inheritance_root(cls): |
| continue |
| if cls == "of_bsn_virtual_port_create_request": # test q_in_q |
| out.write(""" |
| obj = (of_object_t *)%(cls)s_new(%(version)s); |
| { |
| of_object_t *vport = of_bsn_vport_q_in_q_new(%(version)s); |
| %(cls)s_vport_set(obj, vport); |
| of_object_delete(vport); |
| } |
| of_object_dump((loci_writer_f)fprintf, out, obj); |
| of_object_delete(obj); |
| """ % dict(cls=cls, version=of_g.of_version_wire2name[version])) |
| else: |
| out.write(""" |
| obj = (of_object_t *)%(cls)s_new(%(version)s); |
| of_object_dump((loci_writer_f)fprintf, out, obj); |
| of_object_delete(obj); |
| """ % dict(cls=cls, version=of_g.of_version_wire2name[version])) |
| |
| out.write(""" |
| fclose(out); |
| return TEST_PASS; |
| } |
| """) |
| |
| def gen_ident_tests(out): |
| """ |
| Generate tests for identifiers |
| |
| For all idents, instantiate, test version supported macros |
| For flags, set it, test it, clear it, test it. |
| """ |
| out.write(""" |
| /** |
| * Test cases for all flag accessor macros |
| * These only test self consistency (and that they compile) |
| */ |
| int |
| test_ident_macros(void) |
| { |
| int value __attribute__((unused)); |
| uint32_t flags; |
| |
| """) |
| |
| for ident, info in of_g.identifiers.items(): |
| if not identifiers.defined_versions_agree(of_g.identifiers, |
| of_g.target_version_list, |
| ident): |
| # @fixme |
| continue |
| out.write(" value = %s;\n" % ident) |
| for version in of_g.target_version_list: |
| if version in info["values_by_version"].keys(): |
| out.write(" TEST_ASSERT(%s_SUPPORTED(%s));\n" % |
| (ident, of_g.of_version_wire2name[version])) |
| else: |
| out.write(" TEST_ASSERT(!%s_SUPPORTED(%s));\n" % |
| (ident, of_g.of_version_wire2name[version])) |
| if flags.ident_is_flag(ident): |
| # Grab first supported version |
| for version in info["values_by_version"]: |
| break |
| out.write(""" |
| flags = 0; |
| %(ident)s_SET(flags, %(ver_name)s); |
| TEST_ASSERT(flags == %(ident)s_BY_VERSION(%(ver_name)s)); |
| TEST_ASSERT(%(ident)s_TEST(flags, %(ver_name)s)); |
| %(ident)s_CLEAR(flags, %(ver_name)s); |
| TEST_ASSERT(flags == 0); |
| TEST_ASSERT(!%(ident)s_TEST(flags, %(ver_name)s)); |
| """ % dict(ident=ident, ver_name=of_g.of_version_wire2name[version])) |
| |
| out.write(""" |
| return TEST_PASS; |
| } |
| """) |
| |
| def gen_datafiles_tests(out, name): |
| tests = [] |
| for filename in test_data.list_files(): |
| data = test_data.read(filename) |
| if not 'c' in data: |
| continue |
| name = filename[:-5].replace("/", "_") |
| tests.append(dict(name=name, |
| filename=filename, |
| c=data['c'], |
| binary=data['binary'])) |
| |
| util.render_template(out, "test_data.c", tests=tests) |