3

I'm trying to figure out, what is the best way of asserting equality of arrays in CUnit.

The documentation I'm aware of does not contain anything related to arrays at all.

Possibilities I considered so far:

  • Loop through the array elements and check them one-by-one using CU_ASSERT_EQUAL.

  • Simply using CU_ASSERT_EQUAL on the two arrays. According to the mentioned documentation, this should not work, since CU_ASSERT_EQUAL simply translates to ==. Indeed, the following test causes a failure:

   const uchar arr1[] = {1,2};
   const uchar arr2[] = {1,2};
   CU_ASSERT_EQUAL(arr1, arr2);
  • CU_ASSERT_NSTRING_EQUAL seems to work, but the name implies it is meant for strings:
   const uchar arr1[] = {1,2};
   const uchar arr2[] = {1,2};
   CU_ASSERT_NSTRING_EQUAL(arr1, arr2, 2);  /* succeeds: OK */

   const uchar arr1[] = {1,3};
   const uchar arr2[] = {1,2};
   CU_ASSERT_NSTRING_EQUAL(arr1, arr2, 2);  /* fails: OK */

I would be most inclined to use CU_ASSERT_NSTRING_EQUAL.

So questions the questions are the following:

  • Does the solution with CU_ASSERT_NSTRING_EQUAL have any downsides, besides the (slightly) misleading name? (I do know that strings are just arrays, terminated by \0.)
  • Are there any better solutions for this issue?

N.B.: In the related question/answer I could find, it is also suggested to iterate over the elements (although I do not think it is a duplicate for my question, since it did not directly ask what is the best way to check for equality of arrays in general).

Community
  • 1
  • 1
Attilio
  • 1,624
  • 1
  • 17
  • 27

2 Answers2

3

I'm getting to this answer a little late but ...

I couldn't find anything in the documentation related to arrays either. It seems to be a missing feature. You can however use:

CU_ASSERT_EQUAL(0, memcmp(actual, expected, count));

In your example it would be:

CU_ASSERT_EQUAL(0, memcmp(arr1, arr2, 2));

greenbender
  • 838
  • 7
  • 11
0

Why should one use the string comparison assertions?

Ignore the fact that there's a STRING in the name and use CU_ASSERT_NSTRING_EQUAL(actual, expected, count). Specifically in your case, the assertions might be

//Assert that the arrays are the same size.
//If not, they can't have deep equality.
CU_ASSERT_EQUAL(sizeof(arr1), sizeof(arr2));

//Now assert byte-by-byte equality
//for the size of both arrays
CU_ASSERT_NSTRING_EQUAL(arr1, arr2, sizeof(arr1));

Looking at the documentation, you can see that the semantic meaning of CU_ASSERT_NSTRING_EQUAL is "assert that 1st count chars of actual and expected are the same." Recall that a char is just a byte in memory. The fact that you interpret the bytes at &arr1 through &arr + (1 * sizeof(uchar)) as unsigned characters notwithstanding, string comparison is done by incrementing across the characters one by one and checking their byte value. For CU_ASSERT_STRING_EQUAL(actual, expected) this occurs until a null byte is encountered because that's how strings are terminated. For CU_ASSERT_NSTRING_EQUAL(actual, expected, count), a size is passed because you want to inspect count-bytes, regardless of whether a null byte is encountered or not. The types in the array might be larger than a char, but it doesn't matter. Underneath, they're still just a fixed number of bytes contiguously located in memory.

Note that this only works if you can tell the unit test framework the size of the object to inspect. This requires either inspecting the object using sizeof(), or knowing the size of the object and having a pointer to the object (recall that sizeof() a pointer is always the same, regardless of the size of the object pointed-to). For an object which is dynamically-sized, this might not be possible.

But will iterating across the array still work?

Maybe. But you'll need to have an assertion for every index. Again, this will only work if you know the size of the array at compile time (i.e. arrays whose space is acquired by malloc and are located on the heap will have unknown sizes at compile time, and therefore a for-loop of CU_ASSERT_EQUAL assertions won't work). In your case, here's what such a thing might look like

//Assert that the arrays are the same size
//If not, they can't have deep equality.
CU_ASSERT_EQUAL(sizeof(arr1), sizeof(arr2));

//Now assert index-by-index equality
for (size_t i=0; i < sizeof(arr1) / sizeof(uchar); i++)
    CU_ASSERT_EQUAL(arr1[i], arr2[i]);

The problem is that your suite will quickly become filled with assertions for large arrays. You don't want to know each element in the array that doesn't match... you want to know that any element in the array doesn't match. As soon as CU_ASSERT_NSTRING_EQUAL finds a non-match, the test fails. This is not the case for a loop of CU_ASSERT_EQUAL assertions. Since the CUnit assertions are implemented as macros, there's no return value to inspect such that you could break out of the loop. And even if you did a separate non-assertion comparison, you'd still leave the trail of assertions that did complete prior to the first non-matching index.

Matthew Cole
  • 602
  • 5
  • 21
  • 2
    The test `CU_ASSERT_NSTRING_EQUAL("foo\x00one", "foo\x00two", 5);` will PASS since `strncmp` stops the comparison at the first NULL byte and under the hood `CU_ASSERT_NSTRING_EQUAL` uses `strncmp`. – greenbender Mar 21 '18 at 05:16