Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add memory location comparison macros (ck_assert_mem_*) #64

Merged
merged 3 commits into from
Nov 3, 2016
Merged

Add memory location comparison macros (ck_assert_mem_*) #64

merged 3 commits into from
Nov 3, 2016

Conversation

prusnak
Copy link
Contributor

@prusnak prusnak commented Oct 19, 2016

@brarcher
Copy link
Contributor

Jenkins: ok to test
^ necessary to start the remaining of the tests

Copy link
Contributor

@brarcher brarcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I like it. There needs to be a trade-off for how much is printed, as it cannot be boundless.

This change will need some unit tests in order to cover the new API. Can you also provide these unit tests along with your change?

/* The x and y parameter swap in memcmp() is needed to handle >, >=, <, <= operators */
/* Output is limited to 64 bytes (or 128 hex digits) */
#define _ck_assert_mem(X, OP, Y, L) do { \
const char* _ck_x = (const char*)(void*)(X); \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the (void*) cast necessary?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be explicit, in case there is a platform where char!=8bits, should this be uint8_t?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to uint8_t and removed void* cast.

_ck_x_str[_ck_i * 2] = 0; \
_ck_y_str[_ck_i * 2] = 0; \
ck_assert_msg(0 OP memcmp(_ck_y, _ck_x, _ck_l), \
"Assertion '"#X#OP#Y"' failed: "#X"==\"%s\", "#Y"==\"%s\"", _ck_x_str, _ck_y_str); \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am torn between the message containing "#X#OP#Y" or something else which mentions the size of the strings. Should the message mention memcmp, as the documentation uses? For example, "0#OPmemcmp(#X,#Y,#L)"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this from ck_assert_str usage, where strcmp is not mentioned in the output (but it is in the documentation). I would prefer not adding it to the output. However I am fine with adding length somewhere.

const char* _ck_x = (const char*)(void*)(X); \
const char* _ck_y = (const char*)(void*)(Y); \
size_t _ck_l = (L); \
char _ck_x_str[129]; \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the magic numbers used in the macro, e.g. "129", "64", be defined? For example CK_MAX_ASSERT_MEM_PRINT_SIZE=64 which is then used everywhere?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, perhaps define it as such:

#ifndef CK_MAX_ASSERT_MEM_PRINT_SIZE
#define CK_MAX_ASSERT_MEM_PRINT_SIZE 
#endif

That way if a user wishes to override it they may. Another option it to make it a configure option

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

size_t _ck_l = (L); \
char _ck_x_str[129]; \
char _ck_y_str[129]; \
static char _ck_hexdigits[] = "0123456789abcdef"; \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this array be made const?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

char _ck_y_str[129]; \
static char _ck_hexdigits[] = "0123456789abcdef"; \
size_t _ck_i; \
for (_ck_i = 0; _ck_i < ((_ck_l > 64) ? 64 : _ck_l); _ck_i++) { \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the computation for the string size be done before the loop, for readability?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

@prusnak
Copy link
Contributor Author

prusnak commented Oct 20, 2016

Pushed a new commit fixing the issues from above.

@prusnak
Copy link
Contributor Author

prusnak commented Oct 20, 2016

Also I am adding the following for discussion. The way I usually use these macros is like this:

ck_assert_mem_eq(der, fromhex("3007020166020200ee"), 9); 

while the function fromhex is defined like this:

#define FROMHEX_MAXLEN 256

const uint8_t *fromhex(const char *str)
{
    static uint8_t buf[FROMHEX_MAXLEN];
    size_t len = strlen(str) / 2;
    if (len > FROMHEX_MAXLEN) len = FROMHEX_MAXLEN;
    for (size_t i = 0; i < len; i++) {
        uint8_t c = 0;
        if (str[i * 2] >= '0' && str[i*2] <= '9') c += (str[i * 2] - '0') << 4;
        if ((str[i * 2] & ~0x20) >= 'A' && (str[i*2] & ~0x20) <= 'F') c += (10 + (str[i * 2] & ~0x20) - 'A') << 4;
        if (str[i * 2 + 1] >= '0' && str[i * 2 + 1] <= '9') c += (str[i * 2 + 1] - '0');
        if ((str[i * 2 + 1] & ~0x20) >= 'A' && (str[i * 2 + 1] & ~0x20) <= 'F') c += (10 + (str[i * 2 + 1] & ~0x20) - 'A');
        buf[i] = c;
    }
    return buf;
}

Do you think it makes sense to integrate it further in the check library, so we could have something like this?

ck_assert_mem_hex_eq(der, "3007020166020200ee", 9); 

It is much more convenient (especially for longer values, eg. 64 bytes) than using:

ck_assert_mem_hex_eq(der, "\x30\x07\x02\x01\x66\x02\x02\x00\xee", 9); 

@brarcher
Copy link
Contributor

I can appreciate the use case you mention regarding the hex. What gives me pause is it would be another place where a maximum size would be used (for the static buffer). The first place using a max size, the strings which reports buffer contents on a failure, does not result in a problem if more data is used than the buffer size. In that case the differences in the two buffers will simply not be printed, but it will not affect the success or failure of the comparison. However, in the case of the hex based comparison call because it may not be obvious to a caller that the hex string provided has a maximum possible length, passing too many characters may affect the expected result. In addition, if an odd number of characters were passed the last character would be ignored.

From projects where I've compared binary data typically the expected data is in a file, and before the comparison the file's contents are loaded into a buffer. However, those typically are for comparisons that are larger than one would want to type for a unit test.

My opinion is to provide the ck_assert_mem_* calls for now, and if there is a way to quell the concerns listed above maybe a ck_assert_hex call can be added as a separate commit.

@prusnak
Copy link
Contributor Author

prusnak commented Oct 25, 2016

Ok, let's focus on ck_assert_mem_* for now. Do you want some other changes or the commit is good as it is now?

@brarcher
Copy link
Contributor

Do you want some other changes or the commit is good as it is now?

The change will need to have some unit tests accompany it. You can add some test cases under the test_ck_assert_ptr_ne test here:

https://github.com/libcheck/check/blob/master/tests/check_check_sub.c#L555

You will also need to add the test cases around here:

https://github.com/libcheck/check/blob/master/tests/check_check_sub.c#L1098

After that, you will need to determine the expected result of each of the test cases (there should be some tests that pass the check and some that fail) and put the expected result at the end of the pointer-based tests in the master_tests table:

https://github.com/libcheck/check/blob/master/tests/check_check_master.c#L117

Just keep the order of adding the tests to the simple test case and the order in the master_tests table consistent to avoid issues. Look around check_check_sub.c for examples of other tests for the ck_assert* macros for inspiration.

After adding those tests the change should be good to go. Let me know if you have any questions or run into something when adding the unit tests.

@prusnak
Copy link
Contributor Author

prusnak commented Oct 26, 2016

Added unit tests as requested in f9676cf ...

These are basically ck_assert_str_* tests, but using binary data instead of strings.

@@ -553,6 +553,78 @@ START_TEST(test_ck_assert_ptr_ne)
}
END_TEST

START_TEST(test_ck_assert_mem_eq)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there is a limit on the number of characters printed in the assert message, there will need to be a test that check the boundary conditions of the message. Namely, a test comparing 0 bytes (should never produce a message), a test where all the bytes are printed (covered already), a test which is the max number of bytes allowed, and a test with too many bytes. Can you add tests for these cases?

@brarcher
Copy link
Contributor

It would appear that one of the unit tests is not passing. This example shows the following message in the test run output:

/home/travis/build/libcheck/check/tests/check_check_master.c:473:F:Core Tests:test_check_all_msgs:56: Expected Assertion '", got Assertion '"\x00\x00\x00\x00\x01" == s' failed: "\x00\x00\x00\x00\x01" == "0000000001", s == "0000000002"

I believe that means there was no failure expected but a failure did occur. I am not sure the reason for this.

If you would like to reproduce this locally, there is a way to disable the unit tests which take a long time to run. Give the --disable-timeout-tests argument to configure and then the unit tests should run fairly quickly. You could also execute the check_check unit test program directly and check for a non-zero return code.

@prusnak
Copy link
Contributor Author

prusnak commented Oct 27, 2016

I believe that means there was no failure expected but a failure did occur. I am not sure the reason for this.

Ah, there was an error in check_check_master.c. Should have used double backslash in C string.

Also added two new tests: test_ck_assert_mem_zerolen and test_ck_assert_mem_eq_long.

}
END_TEST

START_TEST(test_ck_assert_mem_expr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this test for which the other test do not already test for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

const char *t[] = { "\x00\x00\x00\x00\x01", "\x00\x00\x00\x00\x02" };
int i = -1;
ck_assert_str_eq(s, t[++i]);
ck_assert_str_eq(s, t[i]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are not both of these identical? Is not t[1] never used?

START_TEST(test_ck_assert_mem_eq_long)
{
const char *s = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02";
const char *t = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To easily check that the two arrays are both at least 65 characters long, might it be easier to:

const char s[65] = {0};
const char t[65] = {1};

Copy link
Contributor Author

@prusnak prusnak Oct 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I need a different character at the end of the array, not at the beginning.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fine, it is OK as is.

{ "Simple Tests", CK_FAILURE, "Assertion 's >= t' failed: s == \"0000000001\", t == \"0000000002\"" },
{ "Simple Tests", CK_PASS, "Passed" },
{ "Simple Tests", CK_PASS, "Passed" },
{ "Simple Tests", CK_FAILURE, "Assertion 't == s' failed: t == \"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\", s == \"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a question about the output of an assertion when the data is too long. In this example we see that both the first and second argument look the same though the data is different. When the data is too long and it is truncated should a "..." be added to the end of the two arguments, so it is obvious to the developer that the difference may not be in the portion of the string shown?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 8b1cd61

@@ -772,6 +772,12 @@ CK_DLL_EXP void CK_EXPORT _ck_assert_failed(const char *file, int line,
} \
_ck_x_str[_ck_i * 2] = 0; \
_ck_y_str[_ck_i * 2] = 0; \
if (_ck_i != _ck_l) { \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be more direct to compare _ck_l against _ck_maxl? It may be more intuitive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

@@ -121,7 +121,7 @@ static master_test_t master_tests[] = {
{ "Simple Tests", CK_FAILURE, "Assertion 't > t' failed: t == \"0000000002\", t == \"0000000002\"" },
{ "Simple Tests", CK_FAILURE, "Assertion 's >= t' failed: s == \"0000000001\", t == \"0000000002\"" },
{ "Simple Tests", CK_PASS, "Passed" },
{ "Simple Tests", CK_FAILURE, "Assertion 't == s' failed: t == \"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\", s == \"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"" },
{ "Simple Tests", CK_FAILURE, "Assertion 't == s' failed: t == \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000..\", s == \"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000..\"" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there would be a difference between one check against CK_MAX_ASSERT_MEM_PRINT_SIZE characters and one with CK_MAX_ASSERT_MEM_PRINT_SIZE+1 characters, can there be a test using exactly CK_MAX_ASSERT_MEM_PRINT_SIZE characters to show that there is no .. at the end?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@brarcher
Copy link
Contributor

brarcher commented Nov 1, 2016

I think that this change is good to go, thanks for the contribution.

Would you mind adding documentation for this change here, perhaps in a separate commit? Maybe add it after the description of ck_assert_ptr_eq and ck_assert_ptr_ne.

I'll look into the unit test which is failing. It appears to be failing before this change, so I believe it to be unrelated.

@prusnak
Copy link
Contributor Author

prusnak commented Nov 1, 2016

Added documentation in a separate commit.

@brarcher brarcher merged commit 9084971 into libcheck:master Nov 3, 2016
@brarcher
Copy link
Contributor

brarcher commented Nov 3, 2016

Thanks for the contribution!

@prusnak prusnak deleted the ck_assert_mem branch November 4, 2016 18:08
@brarcher
Copy link
Contributor

brarcher commented Nov 5, 2016

Oh, I forgot to ask, @prusnak . If you are interested would you mind submitting a change for the AUTHORS file, adding an entry for yourself?

@prusnak
Copy link
Contributor Author

prusnak commented Nov 5, 2016

Added in #68

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants