| /*****************************************************************************\ |
| * test_pack_data.c - unit test for pack_data() and unpack_data() |
| ***************************************************************************** |
| * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| * |
| * This file is part of Slurm, a resource management program. |
| * For details, see <https://slurm.schedmd.com/>. |
| * Please also read the included file: DISCLAIMER. |
| * |
| * Slurm is free software; you can redistribute it and/or modify it under |
| * the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * In addition, as a special exception, the copyright holders give permission |
| * to link the code of portions of this program with the OpenSSL library under |
| * certain conditions as described in each individual source file, and |
| * distribute linked combinations including the two. You must obey the GNU |
| * General Public License in all respects for all of the code used other than |
| * OpenSSL. If you modify file(s) with this exception, you may extend this |
| * exception to your version of the file(s), but you are not obligated to do |
| * so. If you do not wish to do so, delete this exception statement from your |
| * version. If you delete this exception statement from all source files in |
| * the program, then also delete it here. |
| * |
| * Slurm is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with Slurm; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| \*****************************************************************************/ |
| |
| #include <check.h> |
| #include <errno.h> |
| #include <float.h> |
| #include <inttypes.h> |
| #include <math.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "slurm/slurm_errno.h" |
| |
| #include "src/common/data.h" |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/pack.h" |
| #include "src/common/pack_data.h" |
| #include "src/common/slurm_protocol_common.h" |
| #include "src/common/xmalloc.h" |
| |
| /* |
| * How deep to probe for the nesting limit. The limit itself is private to |
| * src/common/pack_data.c, so the tests find it rather than copy it. This only |
| * has to sit above any limit that could be considered sane. |
| */ |
| #define DEPTH_PROBE_LIMIT 128 |
| |
| /* how many random buffers to throw at unpack_data() */ |
| #define GARBAGE_ROUNDS 5000 |
| #define GARBAGE_MAX_BYTES 64 |
| |
| /* |
| * The largest entry count that fits a one byte count, so that both it and |
| * the next one up get packed. |
| */ |
| #define COUNT_8_MAX 255 |
| |
| /* |
| * Every byte pack_data() writes as a tag, so that half of the garbage starts |
| * with one and drives the parser past the first byte. Kept here rather than |
| * shared because data_pack_tag_t is private to src/common/pack_data.c. |
| */ |
| static const unsigned char pack_data_tags[] = { |
| 0x01, /* null */ |
| 0x02, 0x03, 0x04, /* list, 8 16 and 32 bit counts */ |
| 0x05, 0x06, 0x07, /* dictionary, 8 16 and 32 bit counts */ |
| 0x08, 0x09, 0x0a, 0x0b, /* integer, 8 16 32 and 64 bits wide */ |
| 0x0c, /* string, its own length behind it */ |
| 0x0d, /* float, 64 bits wide */ |
| 0x0e, /* bool, 8 bits wide */ |
| }; |
| |
| /* every type a data_t can hold, in the order the permutations walk them */ |
| static const data_type_t pack_data_types[] = { |
| DATA_TYPE_NULL, DATA_TYPE_LIST, DATA_TYPE_DICT, DATA_TYPE_INT_64, |
| DATA_TYPE_STRING, DATA_TYPE_FLOAT, DATA_TYPE_BOOL, |
| }; |
| |
| /* |
| * Draw the next pseudo-random number. |
| * |
| * Not rand_r(), which only promises the same sequence within one C library. |
| * The corpus below has to be the same bytes everywhere, or a failure found on |
| * one platform cannot be repeated on another. |
| * |
| * IN/OUT state - generator state, which must not start at zero |
| * RET the next value |
| */ |
| static uint32_t _xorshift32(uint32_t *state) |
| { |
| uint32_t x = *state; |
| |
| x ^= x << 13; |
| x ^= x >> 17; |
| x ^= x << 5; |
| |
| return (*state = x); |
| } |
| |
| /* |
| * Pack data, unpack it into a new data_t, and compare the two. |
| * |
| * The comparison is made twice, because neither half catches what the other |
| * does. data_check_match() converts before it compares, so it sees a lost |
| * value but not a lost type: an integer and its decimal string match. Packing |
| * the copy a second time catches that, since the tag differs, along with a |
| * changed width or a reordered container. pack_data() always writes the same |
| * bytes for the same data_t, so the two packings are comparable. |
| * |
| * IN data - data to round trip |
| * RET true when the unpacked copy matches what went in |
| */ |
| static bool _round_trip(const data_t *data) |
| { |
| buf_t *buffer = init_buf(0); |
| buf_t *again = init_buf(0); |
| data_t *out = data_new(); |
| bool match = false; |
| |
| if (!pack_data(data, SLURM_PROTOCOL_VERSION, buffer)) { |
| const uint32_t len = get_buf_offset(buffer); |
| |
| set_buf_offset(buffer, 0); |
| |
| if (!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer) && |
| !pack_data(out, SLURM_PROTOCOL_VERSION, again)) |
| match = (data_check_match(data, out, false) && |
| (len == get_buf_offset(again)) && |
| !memcmp(get_buf_data(buffer), |
| get_buf_data(again), len)); |
| } |
| |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| FREE_NULL_BUFFER(again); |
| |
| return match; |
| } |
| |
| /* |
| * Try to unpack bytes that were never packed by pack_data(). |
| * IN bytes - buffer contents to unpack |
| * IN len - number of bytes |
| * RET what unpack_data() returned |
| */ |
| static int _unpack_rc(const char *bytes, uint32_t len) |
| { |
| char *data = xmalloc(len); |
| buf_t *buffer; |
| data_t *out = data_new(); |
| int rc = EINVAL; |
| |
| memcpy(data, bytes, len); |
| buffer = create_buf(data, len); |
| |
| rc = unpack_data(out, SLURM_PROTOCOL_VERSION, buffer); |
| |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| |
| return rc; |
| } |
| |
| /* |
| * Check the bytes pack_data() writes, and that they read back. |
| * |
| * The round trip tests cannot see this: pack_data() and unpack_data() move |
| * together, so renumbering a tag or changing which width a value takes leaves |
| * every one of them passing. These are the only assertions that pin the |
| * format itself, which goes on the wire and cannot change. |
| * |
| * IN data - data to pack |
| * IN bytes - exactly what pack_data() must write |
| * IN len - number of bytes, which may hold a NUL |
| * IN label - name for the failure message |
| */ |
| static void _golden(const data_t *data, const char *bytes, uint32_t len, |
| const char *label) |
| { |
| buf_t *buffer = init_buf(0); |
| char *copy = xmalloc(len); |
| buf_t *wire; |
| data_t *out = data_new(); |
| |
| ck_assert_msg(!pack_data(data, SLURM_PROTOCOL_VERSION, buffer), |
| "%s: pack_data() failed", label); |
| ck_assert_msg(get_buf_offset(buffer) == len, |
| "%s: packed %u bytes, expected %u", label, |
| get_buf_offset(buffer), len); |
| ck_assert_msg(!memcmp(get_buf_data(buffer), bytes, len), |
| "%s: packed bytes differ from the expected ones", label); |
| |
| /* and the same bytes, handed back as if they came off the wire */ |
| memcpy(copy, bytes, len); |
| wire = create_buf(copy, len); |
| |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, wire), |
| "%s: unpack_data() failed", label); |
| ck_assert_msg(data_check_match(data, out, false), |
| "%s: unpacked value differs", label); |
| ck_assert_msg(remaining_buf(wire) == 0, "%s: %u bytes left unread", |
| label, remaining_buf(wire)); |
| |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| FREE_NULL_BUFFER(wire); |
| } |
| |
| /* |
| * Check the tag and count a container is packed behind. |
| * |
| * A round trip cannot see this: pack_data() and unpack_data() agree on |
| * whatever width is chosen, so an off-by-one at a count boundary still reads |
| * back correctly. Only the bytes show which width was picked. |
| * |
| * IN data - container to pack |
| * IN head - the tag and count it must start with |
| * IN len - length of head |
| * IN label - name for the failure message |
| */ |
| static void _check_head(const data_t *data, const char *head, uint32_t len, |
| const char *label) |
| { |
| buf_t *buffer = init_buf(0); |
| |
| ck_assert_msg(!pack_data(data, SLURM_PROTOCOL_VERSION, buffer), |
| "%s: pack_data() failed", label); |
| ck_assert_msg(get_buf_offset(buffer) >= len, "%s: packed too little", |
| label); |
| ck_assert_msg(!memcmp(get_buf_data(buffer), head, len), |
| "%s: wrong tag or count width", label); |
| |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| /* |
| * Give data the requested type. |
| * IN/OUT data - data to populate |
| * IN type - type to give it |
| * RET the entry to descend into for a list or a dictionary, NULL for the |
| * types that hold no other data |
| */ |
| static data_t *_set_type(data_t *data, data_type_t type) |
| { |
| switch (type) { |
| case DATA_TYPE_NULL: |
| data_set_null(data); |
| return NULL; |
| case DATA_TYPE_INT_64: |
| data_set_int(data, -12345); |
| return NULL; |
| case DATA_TYPE_STRING: |
| data_set_string(data, "value"); |
| return NULL; |
| case DATA_TYPE_FLOAT: |
| data_set_float(data, 1.5); |
| return NULL; |
| case DATA_TYPE_BOOL: |
| data_set_bool(data, true); |
| return NULL; |
| case DATA_TYPE_LIST: |
| data_set_list(data); |
| return data_list_append(data); |
| case DATA_TYPE_DICT: |
| data_set_dict(data); |
| return data_key_set(data, "key"); |
| default: |
| ck_abort_msg("unexpected type %s", data_type_to_string(type)); |
| return NULL; |
| } |
| } |
| |
| /* |
| * Name the shape being walked, for a failure message. |
| * IN seq - types from the outermost inward |
| * IN len - how many of them |
| * RET the shape as text, valid until the next call |
| */ |
| static const char *_shape(const data_type_t *seq, int len) |
| { |
| static char buf[128]; |
| size_t n = 0; |
| |
| buf[0] = '\0'; |
| |
| for (int i = 0; (i < len) && (n < sizeof(buf)); i++) |
| n += snprintf(buf + n, sizeof(buf) - n, "%s%s", i ? " > " : "", |
| data_type_to_string(seq[i])); |
| |
| return buf; |
| } |
| |
| /* |
| * Nest single entry lists, with a null at the bottom. |
| * IN/OUT data - data to build the nesting into |
| * IN levels - how many lists, which must be at least one |
| */ |
| static void _nest(data_t *data, int levels) |
| { |
| data_t *at = data_set_list(data); |
| |
| for (int i = 1; i < levels; i++) |
| at = data_set_list(data_list_append(at)); |
| |
| data_set_null(data_list_append(at)); |
| } |
| |
| START_TEST(test_pack_data_scalars) |
| { |
| data_t *data = data_new(); |
| |
| data_set_null(data); |
| ck_assert_msg(_round_trip(data), "null"); |
| |
| data_set_bool(data, true); |
| ck_assert_msg(_round_trip(data), "bool true"); |
| |
| data_set_bool(data, false); |
| ck_assert_msg(_round_trip(data), "bool false"); |
| |
| data_set_string(data, ""); |
| ck_assert_msg(_round_trip(data), "empty string"); |
| |
| data_set_string(data, "a string"); |
| ck_assert_msg(_round_trip(data), "string"); |
| |
| data_set_string(data, "\xe2\x82\xac and \xc3\xa9"); |
| ck_assert_msg(_round_trip(data), "utf-8 string"); |
| |
| /* a string of the bytes that are also tags */ |
| data_set_string(data, "\x02\x0c\x0e\x08"); |
| ck_assert_msg(_round_trip(data), "string of tag bytes"); |
| |
| data_set_list(data); |
| ck_assert_msg(_round_trip(data), "empty list"); |
| |
| data_set_dict(data); |
| ck_assert_msg(_round_trip(data), "empty dictionary"); |
| |
| /* an empty key, and one long enough to need more than a byte */ |
| { |
| char long_key[300]; |
| |
| memset(long_key, 'k', sizeof(long_key) - 1); |
| long_key[sizeof(long_key) - 1] = '\0'; |
| |
| data_set_dict(data); |
| data_set_int(data_key_set(data, ""), 1); |
| ck_assert_msg(_round_trip(data), "empty key"); |
| |
| data_set_dict(data); |
| data_set_int(data_key_set(data, long_key), 1); |
| ck_assert_msg(_round_trip(data), "long key"); |
| |
| data_set_dict(data); |
| data_set_int(data_key_set(data, "\xe2\x82\xac"), 1); |
| ck_assert_msg(_round_trip(data), "utf-8 key"); |
| |
| /* 127 characters is the last key the short form holds */ |
| long_key[127] = '\0'; |
| data_set_dict(data); |
| data_set_int(data_key_set(data, long_key), 1); |
| ck_assert_msg(_round_trip(data), "127 character key"); |
| _check_head(data, "\x05\x01\x7f", 3, "short form key length"); |
| |
| long_key[127] = 'k'; |
| long_key[128] = '\0'; |
| data_set_dict(data); |
| data_set_int(data_key_set(data, long_key), 1); |
| ck_assert_msg(_round_trip(data), "128 character key"); |
| _check_head(data, "\x05\x01\x81\x80", 4, |
| "long form key length"); |
| } |
| |
| FREE_NULL_DATA(data); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_floats) |
| { |
| /* |
| * Values that survive the trip. packdouble() stores the value scaled |
| * by FLOAT_MULT rather than the bit pattern, so this is not every |
| * double; see the limit pinned below. |
| */ |
| static const double values[] = { |
| 0.0, -0.0, 1.5, -1.5, 0.1, -0.1, 1e-9, |
| 100.389, 1e300, -1e300, DBL_MIN, INFINITY, -INFINITY, NAN, |
| }; |
| data_t *data = data_new(); |
| data_t *out = data_new(); |
| buf_t *buffer = init_buf(0); |
| |
| for (int i = 0; i < ARRAY_SIZE(values); i++) { |
| data_set_float(data, values[i]); |
| ck_assert_msg(_round_trip(data), "float %g", values[i]); |
| } |
| |
| /* |
| * And the limit itself, so that it cannot quietly get worse. Anything |
| * above DBL_MAX/FLOAT_MULT overflows to infinity on the way in. This |
| * asserts what the format does today rather than what it should do. |
| */ |
| data_set_float(data, DBL_MAX); |
| ck_assert_msg(!pack_data(data, SLURM_PROTOCOL_VERSION, buffer), |
| "pack_data() failed"); |
| set_buf_offset(buffer, 0); |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "unpack_data() failed"); |
| ck_assert_msg( |
| isinf(data_get_float(out)), |
| "DBL_MAX no longer overflows; the FLOAT_MULT limit moved"); |
| |
| FREE_NULL_DATA(data); |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_empty_strings) |
| { |
| /* |
| * The length counts bytes and the terminator is not sent, so nothing |
| * empty puts a NUL on the wire: an empty string is its tag and a zero |
| * length, and an empty key is a zero length alone. Only the bytes |
| * show that, since a round trip is happy either way. |
| */ |
| data_t *data = data_new(); |
| |
| /* |
| * The empty string alone is pinned by test_pack_data_golden. Start |
| * with the one byte case, so the framing is not a coincidence, and |
| * then take the shapes that test does not reach. |
| */ |
| _golden(data_set_string(data, "a"), "\x0c\x01\x61", 3, |
| "one byte string"); |
| |
| data_set_dict(data); |
| data_set_int(data_key_set(data, ""), 1); |
| _golden(data, "\x05\x01\x00\x08\x01", 5, "empty dictionary key"); |
| |
| data_set_dict(data); |
| data_set_string(data_key_set(data, ""), ""); |
| _golden(data, "\x05\x01\x00\x0c\x00", 5, "empty key and value"); |
| |
| data_set_list(data); |
| data_set_string(data_list_append(data), ""); |
| _golden(data, "\x02\x01\x0c\x00", 4, "list holding an empty string"); |
| |
| /* an empty key does not collide with a one byte key */ |
| data_set_dict(data); |
| data_set_int(data_key_set(data, ""), 1); |
| data_set_int(data_key_set(data, "k"), 2); |
| /* the empty key took an entry of its own, so the round trip has two */ |
| ck_assert_int_eq(data_get_dict_length(data), 2); |
| ck_assert_msg(_round_trip(data), "empty key beside a named one"); |
| |
| FREE_NULL_DATA(data); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_int_widths) |
| { |
| /* |
| * An integer is packed at the narrowest width that holds it, so walk |
| * both sides of every width boundary. These are signed, so a value |
| * that narrows must sign extend back to what it was. |
| */ |
| static const int64_t values[] = { |
| 0, |
| 1, |
| -1, |
| INT8_MAX, |
| INT8_MIN, |
| ((int64_t) INT8_MAX + 1), |
| ((int64_t) INT8_MIN - 1), |
| INT16_MAX, |
| INT16_MIN, |
| ((int64_t) INT16_MAX + 1), |
| ((int64_t) INT16_MIN - 1), |
| INT32_MAX, |
| INT32_MIN, |
| ((int64_t) INT32_MAX + 1), |
| ((int64_t) INT32_MIN - 1), |
| INT64_MAX, |
| INT64_MIN, |
| }; |
| data_t *data = data_new(); |
| |
| for (int i = 0; i < ARRAY_SIZE(values); i++) { |
| data_set_int(data, values[i]); |
| ck_assert_msg(_round_trip(data), "int %" PRId64, values[i]); |
| } |
| |
| FREE_NULL_DATA(data); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_golden) |
| { |
| data_t *data = data_new(); |
| |
| _golden(data_set_null(data), "\x01", 1, "null"); |
| |
| _golden(data_set_bool(data, true), "\x0e\x01", 2, "bool true"); |
| _golden(data_set_bool(data, false), "\x0e\x00", 2, "bool false"); |
| |
| /* an integer takes the narrowest width that holds it */ |
| _golden(data_set_int(data, 0), "\x08\x00", 2, "int 0"); |
| _golden(data_set_int(data, -1), "\x08\xff", 2, "int -1"); |
| _golden(data_set_int(data, INT8_MAX), "\x08\x7f", 2, "int 127"); |
| _golden(data_set_int(data, INT8_MAX + 1), "\x09\x00\x80", 3, "int 128"); |
| _golden(data_set_int(data, INT16_MAX), "\x09\x7f\xff", 3, "int 32767"); |
| _golden(data_set_int(data, INT16_MAX + 1), "\x0a\x00\x00\x80\x00", 5, |
| "int 32768"); |
| _golden(data_set_int(data, INT32_MAX), "\x0a\x7f\xff\xff\xff", 5, |
| "int INT32_MAX"); |
| _golden(data_set_int(data, (int64_t) INT32_MAX + 1), |
| "\x0b\x00\x00\x00\x00\x80\x00\x00\x00", 9, "int INT32_MAX + 1"); |
| |
| /* a string carries its length in characters; the NUL is not sent */ |
| _golden(data_set_string(data, ""), "\x0c\x00", 2, "empty string"); |
| _golden(data_set_string(data, "hi"), "\x0c\x02\x68\x69", 4, "string"); |
| |
| /* |
| * packdouble() stores the value scaled by FLOAT_MULT rather than the |
| * bit pattern, so these eight bytes are 1.5 * 1000000 as a double. |
| */ |
| _golden(data_set_float(data, 1.5), |
| "\x0d\x41\x36\xe3\x60\x00\x00\x00\x00", 9, "float 1.5"); |
| |
| _golden(data_set_list(data), "\x02\x00", 2, "empty list"); |
| _golden(data_set_dict(data), "\x05\x00", 2, "empty dictionary"); |
| |
| data_set_list(data); |
| data_set_int(data_list_append(data), 1); |
| _golden(data, "\x02\x01\x08\x01", 4, "list of one"); |
| |
| data_set_dict(data); |
| data_set_bool(data_key_set(data, "k"), true); |
| _golden(data, "\x05\x01\x01\x6b\x0e\x01", 6, "dictionary of one"); |
| |
| FREE_NULL_DATA(data); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_counts) |
| { |
| /* |
| * A container count is packed at the narrowest width that holds it, |
| * so cross the boundary between the 8 and 16 bit counts. The entries |
| * are distinct, so a reordering is caught as well as a miscount. |
| * |
| * The 16 to 32 bit boundary is not covered. It needs about 65535 |
| * entries, and a container that large is quadratic to release: |
| * data.c's _release_data_list_node() walks the list to find each |
| * node's predecessor, so one such list costs about sixteen seconds in |
| * a developer build. test_unpack_data_wide_counts() reads a 32 bit |
| * count back instead, which covers the decoding but not the choice of |
| * width. |
| */ |
| static const struct { |
| uint32_t count; |
| const char *list_head; |
| const char *dict_head; |
| uint32_t head_len; |
| } counts[] = { |
| { COUNT_8_MAX, "\x02\xff", "\x05\xff", 2 }, |
| { (COUNT_8_MAX + 1), "\x03\x01\x00", "\x06\x01\x00", 3 }, |
| }; |
| |
| data_t *data = data_new(); |
| |
| for (int i = 0; i < ARRAY_SIZE(counts); i++) { |
| data_set_list(data); |
| |
| for (uint32_t n = 0; n < counts[i].count; n++) |
| data_set_int(data_list_append(data), n); |
| |
| ck_assert_msg(_round_trip(data), "list of %u", counts[i].count); |
| _check_head(data, counts[i].list_head, counts[i].head_len, |
| "list count width"); |
| } |
| |
| for (int i = 0; i < ARRAY_SIZE(counts); i++) { |
| data_set_dict(data); |
| |
| for (uint32_t n = 0; n < counts[i].count; n++) { |
| char key[16]; |
| |
| snprintf(key, sizeof(key), "%u", n); |
| data_set_int(data_key_set(data, key), n); |
| } |
| |
| ck_assert_msg(_round_trip(data), "dictionary of %u", |
| counts[i].count); |
| _check_head(data, counts[i].dict_head, counts[i].head_len, |
| "dictionary count width"); |
| } |
| |
| FREE_NULL_DATA(data); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_string_widths) |
| { |
| /* |
| * A string carries its own length in the same definite length form a |
| * dictionary key uses, so the short form ends at 127 rather than at |
| * 255. The length counts bytes and not the terminator, so 127 |
| * bytes is the last string to fit it. |
| */ |
| char str[(COUNT_8_MAX * 2)]; |
| data_t *data = data_new(); |
| |
| memset(str, 'x', sizeof(str)); |
| |
| str[127] = '\0'; |
| data_set_string(data, str); |
| ck_assert_msg(_round_trip(data), "string of 127"); |
| _check_head(data, "\x0c\x7f", 2, "short form string length"); |
| |
| str[127] = 'x'; |
| str[128] = '\0'; |
| data_set_string(data, str); |
| ck_assert_msg(_round_trip(data), "string of 128"); |
| _check_head(data, "\x0c\x81\x80", 3, "long form string length"); |
| |
| /* |
| * The other two width transitions. Only the bytes show which form |
| * was written, and the three byte form is the one that splits the |
| * length by hand rather than handing it to a pack function. |
| */ |
| str[128] = 'x'; |
| str[255] = '\0'; |
| data_set_string(data, str); |
| ck_assert_msg(_round_trip(data), "string of 255"); |
| _check_head(data, "\x0c\x81\xff", 3, "last one byte length"); |
| |
| str[255] = 'x'; |
| str[256] = '\0'; |
| data_set_string(data, str); |
| ck_assert_msg(_round_trip(data), "string of 256"); |
| _check_head(data, "\x0c\x82\x01\x00", 4, "first two byte length"); |
| |
| { |
| char *big = xmalloc(0x10001); |
| |
| memset(big, 'x', 0xffff); |
| data_set_string(data, big); |
| ck_assert_msg(_round_trip(data), "string of 65535"); |
| _check_head(data, "\x0c\x82\xff\xff", 4, |
| "last two byte length"); |
| |
| memset(big, 'x', 0x10000); |
| data_set_string(data, big); |
| ck_assert_msg(_round_trip(data), "string of 65536"); |
| _check_head(data, "\x0c\x83\x01\x00\x00", 5, |
| "first three byte length"); |
| xfree(big); |
| } |
| |
| /* a zero length is an empty string, and stays a string */ |
| { |
| char *copy = xmalloc(2); |
| buf_t *buffer; |
| data_t *out = data_new(); |
| |
| memcpy(copy, "\x0c\x00", 2); |
| buffer = create_buf(copy, 2); |
| |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "zero length string was rejected"); |
| ck_assert_msg(data_get_type(out) == DATA_TYPE_STRING, |
| "zero length string did not stay a string"); |
| ck_assert_str_eq(data_get_string(out), ""); |
| |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| FREE_NULL_DATA(data); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_unpack_data_key_lengths) |
| { |
| /* |
| * A key length is written the way X.690 writes a definite length: a |
| * byte under 128 is the length, and above it the low bits count the |
| * bytes that follow. Only the shortest form of a length is valid, so |
| * one length has one spelling on the wire and a message that is |
| * unpacked and packed again comes back the same. |
| */ |
| ck_assert_int_eq(_unpack_rc("\x05\x01\x81\x01\x6b\x0e\x01", 7), |
| SLURM_ERROR); |
| ck_assert_int_eq(_unpack_rc("\x05\x01\x82\x00\x01\x6b\x0e\x01", 8), |
| SLURM_ERROR); |
| |
| /* the indefinite form, which is not a length at all */ |
| ck_assert_int_eq(_unpack_rc("\x05\x01\x80\x6b\x0e\x01", 6), |
| SLURM_ERROR); |
| |
| /* |
| * Wider than four bytes, which would not fit a uint32_t. The length |
| * below is otherwise well formed and the key that follows it is |
| * complete, so nothing but the width check can reject this. Reading |
| * five bytes would also shift a uint32_t by 32, which is undefined. |
| */ |
| { |
| char wide[(2 + 6 + 128 + 2)]; |
| int n = 0; |
| |
| wide[n++] = 0x05; /* DATA_TAG_DICT_8 */ |
| wide[n++] = 0x01; /* one entry */ |
| wide[n++] = 0x85; /* five length bytes follow */ |
| wide[n++] = 0x00; |
| wide[n++] = 0x00; |
| wide[n++] = 0x00; |
| wide[n++] = 0x00; |
| wide[n++] = 0x80; /* a key length of 128 */ |
| |
| for (int i = 0; i < 128; i++) |
| wide[n++] = 'k'; |
| |
| wide[n++] = 0x0e; /* DATA_TAG_BOOL_8 */ |
| wide[n++] = 0x01; |
| |
| ck_assert_int_eq(_unpack_rc(wide, n), SLURM_ERROR); |
| } |
| |
| /* |
| * The terminator is not sent, so a NUL among the bytes was not put |
| * there by pack_data(). Left in, it would truncate the string, and two |
| * dictionary keys differing only past a NUL would collide on the |
| * shorter of the two. |
| */ |
| ck_assert_int_eq(_unpack_rc("\x0c\x05\x41\x42\x00\x43\x44", 7), |
| SLURM_ERROR); |
| ck_assert_int_eq( |
| _unpack_rc("\x05\x02\x01\x61\x08\x01\x03\x61\x00\x62\x08\x02", |
| 12), |
| SLURM_ERROR); |
| |
| /* |
| * The same key twice. data_key_set() would hand back the first |
| * entry, so the second value would replace it and the dictionary |
| * would come back shorter than the wire said. |
| */ |
| ck_assert_int_eq(_unpack_rc("\x05\x02\x01\x6b\x08\x01\x01\x6b\x08\x02", |
| 10), |
| SLURM_ERROR); |
| |
| /* the sentinels, which are reserved rather than sizes */ |
| ck_assert_int_eq(_unpack_rc("\x05\x01\x84\xff\xff\xff\xff", 7), |
| SLURM_ERROR); |
| ck_assert_int_eq(_unpack_rc("\x05\x01\x84\xff\xff\xff\xfe", 7), |
| SLURM_ERROR); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_unpack_data_wide_counts) |
| { |
| /* |
| * A count wider than the entries need is still a count. pack_data() |
| * only writes the narrowest one, so these come from a peer that chose |
| * otherwise, and they are the only coverage of reading a 32 bit |
| * count. |
| */ |
| data_t *out = data_new(); |
| data_t *entry = NULL; |
| |
| /* a 32 bit list count, holding two entries */ |
| { |
| const char bytes[] = "\x04\x00\x00\x00\x02\x08\x01\x08\x02"; |
| const uint32_t len = sizeof(bytes) - 1; |
| char *copy = xmalloc(len); |
| buf_t *buffer; |
| |
| memcpy(copy, bytes, len); |
| buffer = create_buf(copy, len); |
| |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "32 bit list count was rejected"); |
| ck_assert_msg(data_get_type(out) == DATA_TYPE_LIST, |
| "not a list"); |
| ck_assert_int_eq(data_get_list_length(out), 2); |
| ck_assert_msg(remaining_buf(buffer) == 0, "bytes left unread"); |
| |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| /* a 32 bit dictionary count, holding one entry */ |
| { |
| const char bytes[] = "\x07\x00\x00\x00\x01\x01\x6b\x0e\x01"; |
| const uint32_t len = sizeof(bytes) - 1; |
| char *copy = xmalloc(len); |
| buf_t *buffer; |
| |
| memcpy(copy, bytes, len); |
| buffer = create_buf(copy, len); |
| |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "32 bit dictionary count was rejected"); |
| ck_assert_msg(data_get_type(out) == DATA_TYPE_DICT, |
| "not a dictionary"); |
| ck_assert_msg((entry = data_key_get(out, "k")), |
| "key is missing"); |
| ck_assert_msg(data_get_bool(entry), "value is wrong"); |
| ck_assert_msg(remaining_buf(buffer) == 0, "bytes left unread"); |
| |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| FREE_NULL_DATA(out); |
| } |
| |
| END_TEST |
| |
| /* |
| * Round trip every shape that fits within the given depth. |
| * |
| * A list or a dictionary descends one level further; any other type ends the |
| * shape there. Each shape is packed as it is reached, so the shorter ones are |
| * covered on the way down rather than by repeating the walk. |
| * |
| * IN/OUT root - outermost data, packed once per shape |
| * IN/OUT at - entry to give the next type to |
| * IN/OUT seq - types so far, for the failure message |
| * IN level - how deep at sits |
| * IN max - deepest level to build |
| */ |
| static void _walk_shapes(const data_t *root, data_t *at, data_type_t *seq, |
| int level, int max) |
| { |
| if (level >= max) |
| return; |
| |
| for (int i = 0; i < ARRAY_SIZE(pack_data_types); i++) { |
| data_t *next = NULL; |
| |
| seq[level] = pack_data_types[i]; |
| next = _set_type(at, seq[level]); |
| |
| ck_assert_msg(_round_trip(root), "%s", |
| _shape(seq, (level + 1))); |
| |
| if (next) |
| _walk_shapes(root, next, seq, (level + 1), max); |
| } |
| } |
| |
| START_TEST(test_pack_data_depth) |
| { |
| data_t *data = data_new(); |
| data_type_t seq[4] = { 0 }; |
| |
| _walk_shapes(data, data, seq, 0, ARRAY_SIZE(seq)); |
| |
| FREE_NULL_DATA(data); |
| } |
| |
| END_TEST |
| |
| /* |
| * Find the deepest nesting pack_data() accepts. |
| * |
| * The limit is private to pack_data.c. Probing for it keeps the assertions |
| * about behaviour rather than about a constant the test cannot see, and means |
| * a copy of it cannot drift. |
| * |
| * RET the deepest level that packed |
| */ |
| static int _find_max_depth(void) |
| { |
| data_t *data = data_new(); |
| int deepest = 0; |
| |
| for (int levels = 1; levels <= DEPTH_PROBE_LIMIT; levels++) { |
| buf_t *buffer = init_buf(0); |
| int rc = EINVAL; |
| |
| _nest(data, levels); |
| rc = pack_data(data, SLURM_PROTOCOL_VERSION, buffer); |
| FREE_NULL_BUFFER(buffer); |
| |
| if (rc) |
| break; |
| |
| deepest = levels; |
| } |
| |
| FREE_NULL_DATA(data); |
| |
| return deepest; |
| } |
| |
| START_TEST(test_pack_data_max_depth) |
| { |
| const int deepest = _find_max_depth(); |
| data_t *data = data_new(); |
| buf_t *buffer = init_buf(0); |
| |
| /* |
| * Too shallow and a legitimately nested payload stops fitting; too |
| * deep and the recursion is no longer bounded well short of the |
| * stack. The band is what notices the limit being moved. |
| */ |
| ck_assert_msg((deepest >= 8) && (deepest <= 64), |
| "pack_data() accepts %d levels, outside the usable band", |
| deepest); |
| |
| /* whatever it accepts has to survive the trip back */ |
| _nest(data, deepest); |
| ck_assert_msg(_round_trip(data), "nesting at the limit was refused"); |
| |
| /* |
| * One deeper is refused while packing, rather than written into a |
| * buffer that unpack_data() would always reject. |
| */ |
| _nest(data, (deepest + 1)); |
| ck_assert_int_eq(pack_data(data, SLURM_PROTOCOL_VERSION, buffer), |
| SLURM_ERROR); |
| |
| FREE_NULL_DATA(data); |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_null_args) |
| { |
| buf_t *buffer = init_buf(0); |
| data_t *data = data_set_int(data_new(), 1); |
| |
| ck_assert_int_eq(pack_data(NULL, SLURM_PROTOCOL_VERSION, buffer), |
| EINVAL); |
| ck_assert_int_eq(pack_data(data, SLURM_PROTOCOL_VERSION, NULL), EINVAL); |
| ck_assert_int_eq(unpack_data(NULL, SLURM_PROTOCOL_VERSION, buffer), |
| EINVAL); |
| ck_assert_int_eq(unpack_data(data, SLURM_PROTOCOL_VERSION, NULL), |
| EINVAL); |
| |
| FREE_NULL_DATA(data); |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_pack_data_adjacent) |
| { |
| /* |
| * A data_t has to sit in a buffer beside other fields, which is the |
| * reason for packing one at all. Nothing else here would catch an |
| * offset that moved too far or not far enough. |
| */ |
| buf_t *buffer = init_buf(0); |
| data_t *first = data_set_string(data_new(), "first"); |
| data_t *second = data_set_int(data_new(), -12345); |
| data_t *out = data_new(); |
| uint32_t head = 0, tail = 0, packed = 0; |
| |
| pack32(0xdeadbeef, buffer); |
| ck_assert_msg(!pack_data(first, SLURM_PROTOCOL_VERSION, buffer), |
| "first pack_data() failed"); |
| ck_assert_msg(!pack_data(second, SLURM_PROTOCOL_VERSION, buffer), |
| "second pack_data() failed"); |
| pack32(0xcafebabe, buffer); |
| |
| /* |
| * What was written, which is not size_buf(): init_buf() allocates |
| * ahead, so the buffer is far larger than the bytes put in it. |
| */ |
| packed = get_buf_offset(buffer); |
| |
| set_buf_offset(buffer, 0); |
| |
| ck_assert_msg(!unpack32(&head, buffer), "leading sentinel"); |
| ck_assert_int_eq(head, 0xdeadbeef); |
| |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "first unpack_data() failed"); |
| ck_assert_msg(data_check_match(first, out, false), "first value"); |
| |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "second unpack_data() failed"); |
| ck_assert_msg(data_check_match(second, out, false), "second value"); |
| |
| ck_assert_msg(!unpack32(&tail, buffer), "trailing sentinel"); |
| ck_assert_int_eq(tail, 0xcafebabe); |
| |
| ck_assert_msg(get_buf_offset(buffer) == packed, |
| "read %u of the %u bytes written", get_buf_offset(buffer), |
| packed); |
| |
| FREE_NULL_DATA(first); |
| FREE_NULL_DATA(second); |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_unpack_data_reuse) |
| { |
| /* |
| * unpack_data() takes an existing data_t, so a caller reading several |
| * values out of one buffer will hand it the same one each time. What |
| * it held before must go. |
| */ |
| buf_t *buffer = init_buf(0); |
| data_t *dict = data_set_dict(data_new()); |
| data_t *scalar = data_set_int(data_new(), 7); |
| data_t *out = data_new(); |
| |
| data_set_int(data_key_set(dict, "one"), 1); |
| data_set_int(data_key_set(dict, "two"), 2); |
| |
| ck_assert_msg(!pack_data(dict, SLURM_PROTOCOL_VERSION, buffer), |
| "pack_data() failed"); |
| ck_assert_msg(!pack_data(scalar, SLURM_PROTOCOL_VERSION, buffer), |
| "pack_data() failed"); |
| |
| set_buf_offset(buffer, 0); |
| |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "unpack_data() failed"); |
| ck_assert_msg(data_check_match(dict, out, false), "dictionary"); |
| |
| /* the same data_t again, this time taking a scalar */ |
| ck_assert_msg(!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer), |
| "unpack_data() failed"); |
| ck_assert_msg(data_get_type(out) == DATA_TYPE_INT_64, |
| "reused data_t kept its old type"); |
| ck_assert_int_eq(data_get_int(out), 7); |
| |
| FREE_NULL_DATA(dict); |
| FREE_NULL_DATA(scalar); |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_unpack_data_malformed) |
| { |
| /* 0x00 and 0xff are deliberately not tags */ |
| ck_assert_int_eq(_unpack_rc("\x00", 1), SLURM_ERROR); |
| ck_assert_int_eq(_unpack_rc("\xff", 1), SLURM_ERROR); |
| ck_assert_int_eq(_unpack_rc("\x0f", 1), SLURM_ERROR); |
| |
| /* a tag with the value it promised missing */ |
| ck_assert_int_eq(_unpack_rc("\x0b", 1), SLURM_ERROR); |
| ck_assert_int_eq(_unpack_rc("\x0c\x05\x68", 3), SLURM_ERROR); |
| |
| /* containers claiming more entries than the buffer can hold */ |
| ck_assert_int_eq(_unpack_rc("\x04\xff\xff\xff\xff", 5), SLURM_ERROR); |
| ck_assert_int_eq(_unpack_rc("\x02\xc8", 2), SLURM_ERROR); |
| ck_assert_int_eq(_unpack_rc("\x05\x01", 2), SLURM_ERROR); |
| |
| /* a dictionary entry with a key but no value */ |
| ck_assert_int_eq(_unpack_rc("\x05\x01\x01\x6b", 4), SLURM_ERROR); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_unpack_data_too_many) |
| { |
| /* |
| * Depth bounds how deep a message goes, not how wide. Every entry |
| * costs a data_t and a list node that data.c takes with xmalloc() and |
| * cannot refuse, and one wire byte asks for another, so a message |
| * that is small and entirely well formed can still ask for more than |
| * the heap will give. The counts below are honest, which is the |
| * point: nothing but the entry budget can refuse the larger one. |
| * |
| * The budget is private to pack_data.c, so bracket it rather than |
| * name it: a thousand entries is a payload anything would take, and |
| * a million is one nothing should. |
| */ |
| const uint32_t few = 1000, many = 1000000; |
| char *wire = xmalloc(5 + many); |
| |
| wire[0] = 0x04; /* DATA_TAG_LIST_32 */ |
| memset(&wire[5], 0x01, many); /* DATA_TAG_NULL each */ |
| |
| wire[1] = (few >> 24); |
| wire[2] = (few >> 16); |
| wire[3] = (few >> 8); |
| wire[4] = few; |
| ck_assert_int_eq(_unpack_rc(wire, (5 + few)), SLURM_SUCCESS); |
| |
| wire[1] = (many >> 24); |
| wire[2] = (many >> 16); |
| wire[3] = (many >> 8); |
| wire[4] = many; |
| ck_assert_int_eq(_unpack_rc(wire, (5 + many)), SLURM_ERROR); |
| |
| xfree(wire); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_unpack_data_too_deep) |
| { |
| /* |
| * Single entry lists one level past what unpack_data() accepts. |
| * pack_data() will not write this, so the bytes are built here. |
| */ |
| const int levels = (_find_max_depth() + 1); |
| char *nested = xmalloc((levels * 2) + 1); |
| int i = 0; |
| |
| for (int n = 0; n < levels; n++) { |
| nested[i++] = 0x02; /* DATA_TAG_LIST_8 */ |
| nested[i++] = 0x01; /* one entry */ |
| } |
| nested[i++] = 0x01; /* DATA_TAG_NULL */ |
| |
| ck_assert_int_eq(_unpack_rc(nested, i), SLURM_ERROR); |
| |
| xfree(nested); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_safe_unpack_data) |
| { |
| buf_t *buffer = init_buf(0); |
| data_t *in = data_set_string(data_new(), "through the macro"); |
| data_t *out = data_new(); |
| |
| ck_assert_msg(!pack_data(in, SLURM_PROTOCOL_VERSION, buffer), |
| "pack_data() failed"); |
| set_buf_offset(buffer, 0); |
| |
| safe_unpack_data(out, SLURM_PROTOCOL_VERSION, buffer); |
| |
| ck_assert_msg(data_check_match(in, out, false), |
| "safe_unpack_data() did not give back what went in"); |
| |
| FREE_NULL_DATA(in); |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| return; |
| |
| unpack_error: |
| FREE_NULL_DATA(in); |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| ck_abort_msg("safe_unpack_data() took the unpack_error path"); |
| } |
| |
| END_TEST |
| |
| START_TEST(test_unpack_data_garbage) |
| { |
| /* a fixed seed, so that anything this finds can be repeated */ |
| uint32_t seed = 0x5a5a5a5a; |
| |
| for (int i = 0; i < GARBAGE_ROUNDS; i++) { |
| char bytes[GARBAGE_MAX_BYTES]; |
| uint32_t len = 1 + (_xorshift32(&seed) % sizeof(bytes)); |
| char *copy = xmalloc(len); |
| buf_t *buffer; |
| data_t *out = data_new(); |
| |
| for (uint32_t b = 0; b < len; b++) |
| bytes[b] = _xorshift32(&seed); |
| |
| /* half of them open with a tag that unpack_data() knows */ |
| if (i % 2) |
| bytes[0] = pack_data_tags[_xorshift32(&seed) % |
| ARRAY_SIZE(pack_data_tags)]; |
| |
| memcpy(copy, bytes, len); |
| buffer = create_buf(copy, len); |
| |
| /* |
| * Random bytes can be a valid packing by chance, so the return |
| * is not asserted on. An accepted buffer has to repack. A |
| * rejected one is only required to return at all: what the |
| * round buys is that unpack_data() did not crash, abort, |
| * corrupt the heap or hang, which libcheck's fork mode and |
| * timeout report on their own. |
| * |
| * Staying inside the buffer is deliberately not asserted here. |
| * processed <= size is an invariant of the buf_t API, so the |
| * assertion could not fail, and an overread that never |
| * advances processed would satisfy it anyway. Run the corpus |
| * under a sanitizer to check bounds. |
| */ |
| if (!unpack_data(out, SLURM_PROTOCOL_VERSION, buffer)) |
| ck_assert_msg(_round_trip(out), |
| "round %d cannot be repacked", i); |
| |
| FREE_NULL_DATA(out); |
| FREE_NULL_BUFFER(buffer); |
| } |
| } |
| |
| END_TEST |
| |
| /* |
| * Keep the rejection logging out of the test output. |
| * |
| * The error path tests reject thousands of buffers, and every one of them is |
| * logged. Done as a fixture so that the level is put back even when an |
| * assertion ends the test early, which matters under CK_FORK=no. |
| */ |
| static void _quiet_setup(void) |
| { |
| log_options_t quiet = LOG_OPTS_INITIALIZER; |
| |
| quiet.stderr_level = LOG_LEVEL_FATAL; |
| log_alter(quiet, 0, NULL); |
| } |
| |
| static void _quiet_teardown(void) |
| { |
| log_options_t loud = LOG_OPTS_INITIALIZER; |
| |
| loud.stderr_level = LOG_LEVEL_DEBUG5; |
| log_alter(loud, 0, NULL); |
| } |
| |
| extern int main(int argc, char **argv) |
| { |
| int failures; |
| log_options_t log_opts = LOG_OPTS_INITIALIZER; |
| |
| TCase *tcase = tcase_create("pack_data"); |
| TCase *tcase_errors = tcase_create("pack_data_errors"); |
| Suite *suite = suite_create("pack_data"); |
| SRunner *sr = NULL; |
| |
| log_opts.stderr_level = LOG_LEVEL_DEBUG5; |
| log_init("test_pack_data", log_opts, 0, NULL); |
| |
| tcase_add_test(tcase, test_pack_data_scalars); |
| tcase_add_test(tcase, test_pack_data_floats); |
| tcase_add_test(tcase, test_pack_data_empty_strings); |
| tcase_add_test(tcase, test_pack_data_int_widths); |
| tcase_add_test(tcase, test_pack_data_golden); |
| tcase_add_test(tcase, test_pack_data_counts); |
| tcase_add_test(tcase, test_pack_data_string_widths); |
| tcase_add_test(tcase, test_unpack_data_wide_counts); |
| tcase_add_test(tcase, test_pack_data_depth); |
| tcase_add_test(tcase, test_pack_data_max_depth); |
| tcase_add_test(tcase, test_pack_data_null_args); |
| tcase_add_test(tcase, test_pack_data_adjacent); |
| tcase_add_test(tcase, test_unpack_data_reuse); |
| tcase_add_test(tcase, test_safe_unpack_data); |
| |
| /* |
| * The permutation walk and the count tests are not quick, but the |
| * harness kills the whole binary at atf.default_command_timeout, |
| * which is sixty seconds. Stay below it, so an overrun is reported |
| * as a named test failure instead of a kill with no results. |
| */ |
| tcase_set_timeout(tcase, 50); |
| |
| suite_add_tcase(suite, tcase); |
| |
| /* |
| * The rejection tests are noisy, so they get their own case with a |
| * fixture that mutes the log and puts it back however the test ends. |
| */ |
| tcase_add_checked_fixture(tcase_errors, _quiet_setup, _quiet_teardown); |
| tcase_add_test(tcase_errors, test_unpack_data_key_lengths); |
| tcase_add_test(tcase_errors, test_unpack_data_malformed); |
| tcase_add_test(tcase_errors, test_unpack_data_too_deep); |
| tcase_add_test(tcase_errors, test_unpack_data_too_many); |
| tcase_add_test(tcase_errors, test_unpack_data_garbage); |
| |
| tcase_set_timeout(tcase_errors, 50); |
| |
| suite_add_tcase(suite, tcase_errors); |
| |
| sr = srunner_create(suite); |
| srunner_run_all(sr, CK_VERBOSE); |
| failures = srunner_ntests_failed(sr); |
| srunner_free(sr); |
| |
| return failures; |
| } |