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
Additional C test infrastructure #3011
Conversation
@ekasper, is this along the lines of what you envisioned? |
Pretty much ignore the changes to The new functionality is in I've updated my recent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than this, I like it so far.
test/stack_test.c
Outdated
@@ -90,82 +92,67 @@ static int test_int_stack(void) | |||
|
|||
/* Check push and num */ | |||
for (i = 0; i < n; i++) { | |||
if (sk_sint_num(s) != i) { | |||
if (!int_eq(sk_sint_num(s), i, "int stack size %d", i)) { | |||
fprintf(stderr, "test int stack size %d\n", i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this fprintf go away?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I thought I'd replaced this already :( It will go away. @jon-oracle found it soon after I made the initial commit.
test/testutil.c
Outdated
static const char *print_string_maybe_null(const char *s) | ||
{ | ||
return s == NULL ? "(NULL)" : s; | ||
} | ||
|
||
int strings_equal(const char *desc, const char *s1, const char *s2) | ||
int strings_eq(const char *s1, const char *s2, const char *desc, ...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A small nit; considering all other comparisons have singular names so far, it seems odd to have plural "strings" here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'd picked up on this already. Should we use the singular or plural for the function names? They are an argument to the macros so either is possible.
The original function was strings_equal() but that seemed unnatural to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer singular... not sure how to rationalise it, though. Maybe by saying that the name denotes the type, not the objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll change them to singular tomorrow.
I prefer this too :)
test/testutil.c
Outdated
return 1; | ||
} | ||
|
||
int strings_ne(const char *s1, const char *s2, const char *desc, ...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and here
test/stack_test.c
Outdated
@@ -45,6 +45,8 @@ DEFINE_SPECIAL_STACK_OF_CONST(uchar, unsigned char) | |||
DEFINE_STACK_OF(SS) | |||
DEFINE_STACK_OF_CONST(SU) | |||
|
|||
IMPLEMENT_COMPARISONS(char, char, "%c") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see a static variant of IMPLEMENT_COMPARISONS
... it would be fitting here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this here rather than just adding to the common header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the question is do we want all of these test functions defined in testutil.c and used everywhere or do we want them in testutil.h so tests can create individual instances of them?
At the moment there is the flexibility to do either (although local versions aren't static). What is ideal here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all the basic types should go to testutil.h
(as they did). Can you give an example where tests would then still need individual instances? Are you thinking typedef'ed types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's exactly what I had in mind. Thanks so much for taking this on!
test/stack_test.c
Outdated
@@ -45,6 +45,8 @@ DEFINE_SPECIAL_STACK_OF_CONST(uchar, unsigned char) | |||
DEFINE_STACK_OF(SS) | |||
DEFINE_STACK_OF_CONST(SU) | |||
|
|||
IMPLEMENT_COMPARISONS(char, char, "%c") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this here rather than just adding to the common header?
test/testutil.h
Outdated
*/ | ||
int strings_equal(const char *desc, const char *s1, const char *s2); | ||
int bool_true(int, const char *desc, ...); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe test_true
and test_false
?
On naming, how about calling them all with a TEST_
prefix to make it stand out that these are test macros:
TEST_int_eq
TEST_true
TEST_false
TEST_ptr_not_null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And on pointers specifically, I'd probs call this TEST_ptr
- less typing, and I think if (!TEST_ptr(p))
is easier to read than the double negative if (!TEST_ptr_not_null(p))
.
We may also want an aborting flavour of this one. It's bad value for effort to write all the extra goto end
boilerplate to exit cleanly on say malloc failures in tests (which just never happen). You don't have to do it in this PR, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ekasper, how would you rename ptr_null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need its own macro? You could simply use TEST_ptr_eq(p, NULL)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so you were thinking TEST_ptr
in addition to TEST_ptr_eq
and TEST_ptr_ne
? That escaped me, and in that case I have no concerns in the matter...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got some embryonic ideas as to how to do automated malloc testing -- i.e. run through all the calls to malloc and cause each to fail in turn. Having test cases that abort early due to this would be detrimental to the effort.
Are we agreed to prefix these functions with TEST_ and to shorten the ptr not null name?
test/testutil.h
Outdated
|
||
/* | ||
* Check a boolean result for being true or false. | ||
* They return 1 if the condition is true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"... if the condition is true (i.e., the value is non-zero)".
Within OpenSSL, 0 sometimes means true :(
test/testutil.h
Outdated
* the data type that was tested, |fmt| is a system provided printf format with | ||
* arguments that spells out the failure details. | ||
*/ | ||
#if defined(__GNUC__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fail_message
method signature is hard to comprehend. Can you give an example in the comments how it is supposed to be used, and what the resulting output looks like?
If this is not meant to be used by tests (but only by the other macros), then perhaps move into a separate header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming that the output of fail_message is in a dreadful format and will be changed. I'll add better descriptions as to what its arguments are and do.
test/testutil.c
Outdated
@@ -145,19 +145,110 @@ int run_tests(const char *test_prog_name) | |||
return EXIT_SUCCESS; | |||
} | |||
|
|||
void fail_message(const char *desc, va_list args, const char *type, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally I'd like to see __FILE__
and __LINE__
here. You'd need to move the implementation to a header, though.
With __FILE__
and __LINE__
we probably won't even need desc
in most cases, so we could make custom descriptions optional (less typing):
ret = TEST_int_eq(a, b);
And optionally
ret = TEST_int_eq_msg(a[i], b[i], "%d th element mismatch", i);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how adding FILE & LINE to the arguments is going to be possible. We'd want macros to do this which then call the underlying functions with the extra arguments. How do I generate the macros automatically? Generating the functions is easy enough and doing macros for predefined ones would be possible (by listing them out). How would I go with e.g. IMPLEMENT_COMPARISONS(struct fred *, fredptrs, "%p")
I'd need to generate fredpts_eq(...) and the wrapper macro TEST_fredpts_eq(...).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there even an OpenSSL approved method of varadic macros?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not as far as I know. That came with later C standards...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be possible to have macro versions of these without the description or at least without the description with arguments. That might be sufficient. I'll mulled it over a bit....
test/testutil.h
Outdated
DEFINE_COMPARISONS(type, name, fmt) | ||
|
||
/* | ||
* Define eight comparisons between pairs of integers and unsigned integers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be nice if you could spell out, at least once here, what the resulting method signatures are.
test/ssl_test.c
Outdated
ret &= strings_equal("ExpectedNPNProtocol", | ||
test_ctx->expected_npn_protocol, | ||
result->client_npn_negotiated); | ||
ret &= strings_eq(result->client_npn_negotiated, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's an idea for a future PR: collect all errors in a static variable and make each test automatically fail if one of the test assertions fails.
Not sure how ugly the implementation would be with the tooling we have (C90) but worth a thought.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Collecting the errors wouldn't be too hard. A stack of strings, malloc buffers and sprintf into them.
Fail if the stack isn't empty.
b3f34af
to
5f4105f
Compare
|
16b7603
to
d9c6f75
Compare
How's this? I've added TEST_ macros that use the description field to display the file and line number -- the user doesn't get to provide a description here but they can always fall back to the underlying function directly for this. I've updated some of the tests in |
we have the tooling to do malloc failures; look at the code in crypto/mem.c |
No varadic macro support.
|
I've seen the crypto mem code, it is sufficient. I'm still figuring out a good way to attack the problem so such testing can become integral to the test framework. There's little point doing ad-hoc tests, each implemented differently. It's a WIP but I'm reasonably confident it's workable. But that is for later. |
d82e65e
to
61dcece
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool!
test/testutil.h
Outdated
* The following macros provide wrapper calls to the test functions with | ||
* a default description that indicates the file and line number of the error. | ||
*/ | ||
#define TEST_int_eq(a, b) test_int_eq(a, b, "%s: %d", __FILE__, __LINE__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good! However I'd still like to preserve file and line numbers for the custom error messages too. Now we can't do that in general (because no support for variadic macros in C90) but you could do something like this I think?
# if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L
# define TEST_error test_error_c90
#else
# define TEST_error(...) test_error(__FILE__, __LINE__, __VA_ARGS__)
#endif
where test_error_c90
takes a format string and args, and test_error
additionally takes the file and line number.
Then hide the lowercase functions in a separate internal header (testutil_internal.h
) so that tests don't use them directly. Code that previously called test_int_eq
would do
if (!TEST_int_eq(a[i], b[i])) {
TEST_error("%d th element mismatch", i);
}
Or you could also provide a macro per comparison, TEST_int_eq_msg(a, b, msg)
, etc.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced that TEST_error
needs to show the file and line number. With the usage example given, the file and line will be produced by TEST_int_eq
and would be redundant. Perhaps call the function TEST_extra
or TEST_info
I don't like the TEST_int_eq_msg
suggestion because we don't have variadic macros so there can be no arguments to the message.
Are you suggesting hiding the test_xxx functions in an header but still creating them in testutil.c ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant you'd do TEST_int_eq_msg
same as TEST_error
- variadic macros where you can, and fall back to a function (losing the file and line number) otherwise. I think that works?
Re file and line numbers, I don't think they ever hurt. I'd probably have TEST_error
for errors (starts with ERROR or FAIL as you have it) and TEST_info
for info (starts with INFO) to make it easy to grep for failure lines - but I don't really have a strong opinion. Whatever feels most natural to you!
Re test_xxx functions, I think so? It's not so important - I only suggested moving the declarations to another header to make it easier to find the public bits you're supposed to use in tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the TEST_xxx_msg flavours can be made to work, it will introduce quite a bit of bloat to the header file -- is the gain worthwhile? The if (TEST_xxx()) TEST_ERROR()
sequence seems reasonable and sufficiently terse. At least to me. I kind of suspect that the TEST_xxx__msg versions won't be heavily used -- people often seem to not like writing tests and when they do, they make them as short and unhelpful as possible. Slightly exaggerated perhaps.
You're right, the line numbers won't hurt. I'll add the info versions.
The declarations need to be visible in testutil.h to avoid compiler warnings about undefined functions. Whether they are in another .h file or not seems to be the concern. I'd don't have a strong feeling either way here.
test/testutil.h
Outdated
#define TEST_uchar_gt(a, b) test_uchar_gt(a, b, "%s: %d", __FILE__, __LINE__) | ||
#define TEST_uchar_ge(a, b) test_uchar_ge(a, b, "%s: %d", __FILE__, __LINE__) | ||
|
||
#define TEST_lint_eq(a, b) test_lint_eq(a, b, "%s: %d", __FILE__, __LINE__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest long
and ulong
("lint" makes me think of linters).
test/testutil.c
Outdated
DEFINE_COMPARISONS(unsigned int, uint, "%u") | ||
DEFINE_COMPARISONS(char, char, "%c") | ||
DEFINE_COMPARISONS(unsigned char, uchar, "%u") | ||
DEFINE_COMPARISONS(long int, lint, "%ld") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simply long
and unsigned long
(drop the int
).
I think this addresses the next round of changes. I've added the TEST_error macro and hidden the definition macros (in testutil.c rather than a separate header file). |
Regarding the TEST_xxx_msg flavours, I can't help but feel that they aren't worthwhile. |
6b7f617
to
c548cc1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice call on testing the tests!
This looks good to me, a few small comments left. I'll let the second reviewer take a fresh look, then let's see it in action.
You may want to post to openssl-dev@ pointing out this work, so devs know to start using this over printf.
test/testutil.c
Outdated
PRINTF_FORMAT(5, 6); | ||
|
||
static void test_fail_message_va(int error, const char *file, int line, | ||
const char *type, const char *fmt, va_list ap) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: bad indent
I'd just pass in const char* prefix
rather than int error
, makes the call site more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion :)
test/testutil.c
Outdated
|
||
for (i=0; i<2*n; i++) { | ||
unsigned char c = (i & 1) != 0 ? p[i / 2] & 15 : p[i / 2] >> 4; | ||
out[i] = c["0123456789abcdef"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this c90?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's C since way back. A little quirk of the language. Ever tried this?
#include <stdio.h>
int main()
{
printf("%c", 3["123456789"]);
}
Of course, the other way around, "123456789"[3]
, is just as possible and equivalent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, let's flip it around to avoid questions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This originally comes from K&R C and has remained ever since. a[b]
is defined to be *(a + b)
. Thus, the order doesn't matter.
Little quirk in my coding to hex conversions...
test/cipherlist_test.c
Outdated
fprintf(stderr, "Wrong cipher at position %d: expected %x, " | ||
"got %x\n", i, expected_cipher_id, cipher_id); | ||
if (!TEST_int_eq(cipher_id, expected_cipher_id)) { | ||
TEST_info("Wrong cipher at position %d: expected %x, " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TEST_error
? (Everywhere where errors are logged?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TEST_int_eq will produce the error message. This is extra additional info: the position where the error occurred. I don't think it should raise a second error, since it isn't. I'll get rid of the duplicated information though. There are probably a few cases like this still.
*/ | ||
|
||
/* | ||
* Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we avoid custom copyright statements in file headers these days. (The CLA does not transfer copyright - just that additional copyrights don't need to be listed in our headers.)
acbe207
to
1460a7f
Compare
Should be good to go. Run I've also updated test/README to not include the bit about editing the makefile and to have a short section about this. |
Any thoughts about dedicating a code health Tuesday to modifying existing test cases to use this infrastructure more uniformly? |
@pauli-oracle that's a great idea! I'll push it. Looks like @levitte;s conerns were address. I also approve and can merge. |
Thanks @richsalz. The single build failure is due to a problem with documentation (i.e. not part of this PR). |
I needed these changes:
And also the test_test recipe needs to be changed. Not sure how to say "failure is success" @levitte ? |
Sorry, I hadn't pushed that capitalisation change yet. I realised I'd not executed those two functions in the test. Done now. There is no need to modify the recipe, the |
This could be a pain when grepping for ERRORs. I also just noticed that there is no test name in the diagnostics... |
TEST_INFO is a struct, as you saw I assume.Do you want to push a change to fix the logging issue? |
I've fixed the test_test.c issue. I'm not concerned about the test name -- we've got file/line number information in the output already so it isn't essential. The error messages are a little busy already. |
So we just need a second reviewer for that last bit.
|
Both lines of it :) |
Which two lines? |
test/test_test.c lines 263 & 264. Well, actually that whole six line function and the line at the bottom that adds it as a test. But only two lines of content :) |
and
|
+1 on the "last bit". |
Reviewed-by: Richard Levitte <levitte@openssl.org> Reviewed-by: Emilia Käsper <emilia@openssl.org> Reviewed-by: Tim Hudson <tjh@openssl.org> Reviewed-by: Rich Salz <rsalz@openssl.org> (Merged from #3011)
merged. thanks for this, and the reviews folks! |
+1 on a Code Health Tuesday, it'd give everyone a chance to learn the framework. Also, we have a bunch of really old tests (for example, ectest.c) that could really use some clean-up. Thanks again for this contribution, really neat! |
The other code health thought I've had is "fix an example day". I.e. go through the examples and get them working and bring them up to date or write new examples. |
Very preliminary test infrastructure additions.
Rather than charging full steam ahead, I'd prefer some feedback to see if this is along desirable lines...
The intention is to provide a suite of functions along the lines of the existing strings_equal routine that test a condition and print a uniformly formatted message if it is false.