diff options
author | joshua <> | 2025-05-21 08:57:13 +0000 |
---|---|---|
committer | joshua <> | 2025-05-21 08:57:13 +0000 |
commit | 18475d634569c6d51839b297c41b2a4fa487df13 (patch) | |
tree | 1fd293c8abe2b36faafb9680953c04e06ca524c7 /src | |
parent | cef53fabc81a75137b27740f687ac1d22bfdfdf2 (diff) | |
download | openbsd-18475d634569c6d51839b297c41b2a4fa487df13.tar.gz openbsd-18475d634569c6d51839b297c41b2a4fa487df13.tar.bz2 openbsd-18475d634569c6d51839b297c41b2a4fa487df13.zip |
Add initial regress test framework
Add a test framework for use in LibreSSL regression tests. This test
framework aims to be as lightweight and as simple to use as possible.
The design is mostly inspired by Go's test system, and aims to be a
drop-in utility in most existing regress tests.
ok jsing tb beck
Diffstat (limited to 'src')
-rw-r--r-- | src/regress/lib/libcrypto/test/test.c | 225 | ||||
-rw-r--r-- | src/regress/lib/libcrypto/test/test.h | 132 | ||||
-rw-r--r-- | src/regress/lib/libcrypto/test/test_util.c | 51 |
3 files changed, 408 insertions, 0 deletions
diff --git a/src/regress/lib/libcrypto/test/test.c b/src/regress/lib/libcrypto/test/test.c new file mode 100644 index 0000000000..b48711919d --- /dev/null +++ b/src/regress/lib/libcrypto/test/test.c | |||
@@ -0,0 +1,225 @@ | |||
1 | /* $OpenBSD: test.c,v 1.1 2025/05/21 08:57:13 joshua Exp $ */ | ||
2 | /* | ||
3 | * Copyright (c) 2025 Joshua Sing <joshua@joshuasing.dev> | ||
4 | * | ||
5 | * Permission to use, copy, modify, and distribute this software for any | ||
6 | * purpose with or without fee is hereby granted, provided that the above | ||
7 | * copyright notice and this permission notice appear in all copies. | ||
8 | * | ||
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
16 | */ | ||
17 | |||
18 | #include <err.h> | ||
19 | #include <libgen.h> | ||
20 | #include <stdarg.h> | ||
21 | #include <stdio.h> | ||
22 | #include <stdlib.h> | ||
23 | #include <string.h> | ||
24 | #include <unistd.h> | ||
25 | |||
26 | #include "test.h" | ||
27 | |||
28 | struct test { | ||
29 | struct test *parent; | ||
30 | char *name; | ||
31 | FILE *out; | ||
32 | int skipped; | ||
33 | int failed; | ||
34 | }; | ||
35 | |||
36 | static struct test * | ||
37 | test_new(struct test *pt, const char *name) | ||
38 | { | ||
39 | struct test *t; | ||
40 | |||
41 | if ((t = calloc(1, sizeof(*t))) == NULL) | ||
42 | err(1, "calloc"); | ||
43 | |||
44 | if (name != NULL) { | ||
45 | if ((t->name = strdup(name)) == NULL) | ||
46 | err(1, "strdup"); | ||
47 | } | ||
48 | |||
49 | t->out = pt->out; | ||
50 | t->parent = pt; | ||
51 | |||
52 | return t; | ||
53 | } | ||
54 | |||
55 | struct test * | ||
56 | test_init(void) | ||
57 | { | ||
58 | struct test *t; | ||
59 | char *tmp_file; | ||
60 | int out_fd; | ||
61 | char *v; | ||
62 | |||
63 | t = test_new(NULL, NULL); | ||
64 | t->out = stderr; | ||
65 | |||
66 | if (((v = getenv("TEST_VERBOSE")) != NULL) && strcmp(v, "0") != 0) | ||
67 | return t; | ||
68 | |||
69 | /* Create a temporary file for logging in non-verbose mode */ | ||
70 | if ((tmp_file = strdup("/tmp/libressl-test.XXXXXXXX")) == NULL) | ||
71 | err(1, "strdup"); | ||
72 | if ((out_fd = mkstemp(tmp_file)) == -1) | ||
73 | err(1, "mkstemp"); | ||
74 | |||
75 | unlink(tmp_file); | ||
76 | if ((t->out = fdopen(out_fd, "w+")) == NULL) | ||
77 | err(1, "fdopen"); | ||
78 | |||
79 | return t; | ||
80 | } | ||
81 | |||
82 | static void | ||
83 | test_cleanup(struct test *t) | ||
84 | { | ||
85 | free(t->name); | ||
86 | free(t); | ||
87 | } | ||
88 | |||
89 | int | ||
90 | test_result(struct test *t) | ||
91 | { | ||
92 | int failed = t->failed; | ||
93 | |||
94 | if (t->parent == NULL && t->out != stderr) | ||
95 | fclose(t->out); | ||
96 | |||
97 | test_cleanup(t); | ||
98 | |||
99 | return failed; | ||
100 | } | ||
101 | |||
102 | void | ||
103 | test_fail(struct test *t) | ||
104 | { | ||
105 | t->failed = 1; | ||
106 | |||
107 | /* Also fail parent. */ | ||
108 | if (t->parent != NULL) | ||
109 | test_fail(t->parent); | ||
110 | } | ||
111 | |||
112 | static void | ||
113 | test_vprintf(struct test *t, const char *fmt, va_list ap) | ||
114 | { | ||
115 | if (vfprintf(t->out, fmt, ap) == -1) | ||
116 | err(1, "vfprintf"); | ||
117 | } | ||
118 | |||
119 | void | ||
120 | test_printf(struct test *t, const char *fmt, ...) | ||
121 | { | ||
122 | va_list ap; | ||
123 | |||
124 | va_start(ap, fmt); | ||
125 | test_vprintf(t, fmt, ap); | ||
126 | va_end(ap); | ||
127 | } | ||
128 | |||
129 | static void | ||
130 | test_vlogf_internal(struct test *t, const char *label, const char *func, | ||
131 | const char *file, int line, const char *fmt, va_list ap) | ||
132 | { | ||
133 | char *msg = NULL; | ||
134 | char *l = ": "; | ||
135 | const char *filename; | ||
136 | |||
137 | if (label == NULL) { | ||
138 | label = ""; | ||
139 | l = ""; | ||
140 | } | ||
141 | |||
142 | if (vasprintf(&msg, fmt, ap) == -1) | ||
143 | err(1, "vasprintf"); | ||
144 | |||
145 | if ((filename = strrchr(file, '/')) != NULL) | ||
146 | filename++; | ||
147 | else | ||
148 | filename = file; | ||
149 | |||
150 | test_printf(t, "%s [%s:%d]%s%s: %s\n", | ||
151 | func, filename, line, l, label, msg); | ||
152 | |||
153 | free(msg); | ||
154 | } | ||
155 | |||
156 | void | ||
157 | test_logf_internal(struct test *t, const char *label, const char *func, | ||
158 | const char *file, int line, const char *fmt, ...) | ||
159 | { | ||
160 | va_list ap; | ||
161 | |||
162 | va_start(ap, fmt); | ||
163 | test_vlogf_internal(t, label, func, file, line, fmt, ap); | ||
164 | va_end(ap); | ||
165 | } | ||
166 | |||
167 | void | ||
168 | test_skip(struct test *t, const char *reason) | ||
169 | { | ||
170 | t->skipped = 1; | ||
171 | test_printf(t, "%s\n", reason); | ||
172 | } | ||
173 | |||
174 | void | ||
175 | test_skipf(struct test *t, const char *fmt, ...) | ||
176 | { | ||
177 | va_list ap; | ||
178 | |||
179 | t->skipped = 1; | ||
180 | |||
181 | va_start(ap, fmt); | ||
182 | test_vprintf(t, fmt, ap); | ||
183 | if (fputc('\n', t->out) == EOF) | ||
184 | err(1, "fputc"); | ||
185 | va_end(ap); | ||
186 | } | ||
187 | |||
188 | void | ||
189 | test_run(struct test *pt, const char *name, test_run_func *fn, const void *arg) | ||
190 | { | ||
191 | struct test *t = test_new(pt, name); | ||
192 | char *status = "PASS"; | ||
193 | char buf[1024]; | ||
194 | size_t buflen; | ||
195 | int ferr; | ||
196 | |||
197 | /* Run test */ | ||
198 | test_printf(t, "=== RUN %s\n", t->name); | ||
199 | fn(t, arg); | ||
200 | |||
201 | if (t->skipped) | ||
202 | status = "SKIP"; | ||
203 | if (t->failed) | ||
204 | status = "FAIL"; | ||
205 | |||
206 | test_printf(t, "--- %s: %s\n\n", status, t->name); | ||
207 | |||
208 | /* Print result of test */ | ||
209 | if (t->failed && t->out != stderr) { | ||
210 | /* Copy logs to stderr */ | ||
211 | rewind(t->out); | ||
212 | while ((buflen = fread(buf, 1, sizeof(buf), t->out)) > 0) | ||
213 | fwrite(buf, 1, buflen, stderr); | ||
214 | if ((ferr = ferror(t->out)) != 0) | ||
215 | errx(1, "ferror: %d", ferr); | ||
216 | } | ||
217 | |||
218 | if (t->out != NULL && t->out != stderr) { | ||
219 | /* Reset output file */ | ||
220 | rewind(t->out); | ||
221 | ftruncate(fileno(t->out), 0); | ||
222 | } | ||
223 | |||
224 | test_cleanup(t); | ||
225 | } | ||
diff --git a/src/regress/lib/libcrypto/test/test.h b/src/regress/lib/libcrypto/test/test.h new file mode 100644 index 0000000000..e6cfec80ea --- /dev/null +++ b/src/regress/lib/libcrypto/test/test.h | |||
@@ -0,0 +1,132 @@ | |||
1 | /* $OpenBSD: test.h,v 1.1 2025/05/21 08:57:13 joshua Exp $ */ | ||
2 | /* | ||
3 | * Copyright (c) 2025 Joshua Sing <joshua@joshuasing.dev> | ||
4 | * | ||
5 | * Permission to use, copy, modify, and distribute this software for any | ||
6 | * purpose with or without fee is hereby granted, provided that the above | ||
7 | * copyright notice and this permission notice appear in all copies. | ||
8 | * | ||
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
16 | */ | ||
17 | |||
18 | #ifndef HEADER_TEST_H | ||
19 | #define HEADER_TEST_H | ||
20 | |||
21 | #include <stdint.h> | ||
22 | |||
23 | struct test; | ||
24 | |||
25 | /* | ||
26 | * test_init creates a new root test struct. | ||
27 | * | ||
28 | * Additional tests may be run under the root test struct by calling test_run. | ||
29 | * | ||
30 | * If the TEST_VERBOSE environment variable is set and not equal to "0", then | ||
31 | * verbose mode will be enabled and all test logs will be written to stderr. | ||
32 | */ | ||
33 | struct test *test_init(void); | ||
34 | |||
35 | /* | ||
36 | * test_result cleans up after all tests have completed and returns an | ||
37 | * appropriate exit code indicating the result of the tests. | ||
38 | */ | ||
39 | int test_result(struct test *_t); | ||
40 | |||
41 | /* | ||
42 | * test_run_func is an individual test function. It is passed the test struct | ||
43 | * and an arbitrary argument which may be passed when test_run is called. | ||
44 | */ | ||
45 | typedef void (test_run_func)(struct test *_t, const void *_arg); | ||
46 | |||
47 | /* | ||
48 | * test_fail marks the test and its parents as failed. | ||
49 | */ | ||
50 | void test_fail(struct test *_t); | ||
51 | |||
52 | /* | ||
53 | * test_printf prints a test log message. When in verbose mode, the log message | ||
54 | * will be written to stderr, otherwise it will be buffered and only written to | ||
55 | * stderr if the test fails. | ||
56 | * | ||
57 | * This printf will write directly, without any additional formatting. | ||
58 | */ | ||
59 | void test_printf(struct test *_t, const char *_fmt, ...); | ||
60 | __attribute__((__format__ (printf, 2, 3))) | ||
61 | __attribute__((__nonnull__ (2))); | ||
62 | |||
63 | /* | ||
64 | * test_logf_internal prints a test log message. When in verbose mode, the | ||
65 | * log message will be written to stderr, otherwise it will be buffered and | ||
66 | * only written to stderr if the test fails. | ||
67 | * | ||
68 | * label is an optional label indicating the severity of the log. | ||
69 | * func, file and line are used to show where the log comes from and are | ||
70 | * automatically set in the test log macros. | ||
71 | * | ||
72 | * This function should never be called directly. | ||
73 | */ | ||
74 | void test_logf_internal(struct test *_t, const char *_label, const char *_func, | ||
75 | const char *_file, int _line, const char *_fmt, ...); | ||
76 | __attribute__((__format__ (printf, 6, 7))) | ||
77 | __attribute__((__nonnull__ (6))); | ||
78 | |||
79 | /* | ||
80 | * test_logf prints an informational log message. When in verbose mode, the log | ||
81 | * will be written to stderr, otherwise it will be buffered and only written to | ||
82 | * stderr if the test fails. | ||
83 | */ | ||
84 | #define test_logf(t, fmt, ...) \ | ||
85 | test_logf_internal(t, NULL, __func__, __FILE__, __LINE__, fmt, ##__VA_ARGS__) | ||
86 | |||
87 | /* | ||
88 | * test_errorf prints an error message. It will also cause the test to fail. | ||
89 | * If the test cannot proceed, it is recommended to return or goto a cleanup | ||
90 | * label. | ||
91 | * | ||
92 | * Tests should not fail-fast if continuing will provide more detailed | ||
93 | * information about what is broken. | ||
94 | */ | ||
95 | #define test_errorf(t, fmt, ...) \ | ||
96 | test_logf_internal(t, "ERROR", __func__, __FILE__, __LINE__, fmt, ##__VA_ARGS__); \ | ||
97 | test_fail(t) | ||
98 | |||
99 | /* | ||
100 | * test_skip marks the test as skipped. Once called, the test should return. | ||
101 | */ | ||
102 | void test_skip(struct test *_t, const char *_reason); | ||
103 | |||
104 | /* | ||
105 | * test_skipf marks the test as skipped with a formatted reason. Once called, | ||
106 | * the test should return. | ||
107 | */ | ||
108 | void test_skipf(struct test *_t, const char *_fmt, ...); | ||
109 | __attribute__((__format__ (printf, 2, 3))) | ||
110 | __attribute__((__nonnull__ (2))); | ||
111 | |||
112 | /* | ||
113 | * test_run runs a test function. It will create a new test struct with the | ||
114 | * given test as the parent. An argument may be provided to pass data to the | ||
115 | * test function, otherwise NULL should be passed. | ||
116 | * | ||
117 | * Each test should have a unique and informational name. | ||
118 | */ | ||
119 | void test_run(struct test *_t, const char *_name, test_run_func *_fn, const void *_arg); | ||
120 | |||
121 | /* | ||
122 | * test_hexdump prints the given data as hexadecimal. | ||
123 | */ | ||
124 | void test_hexdump(struct test *_t, const unsigned char *_buf, size_t _len); | ||
125 | |||
126 | /* | ||
127 | * test_hexdiff prints the given data as hexadecimal. If a second comparison | ||
128 | * buffer is not NULL, any differing bytes will be marked with an astrix. | ||
129 | */ | ||
130 | void test_hexdiff(struct test *_t, const uint8_t *_buf, size_t _len, const uint8_t *_compare); | ||
131 | |||
132 | #endif /* HEADER_TEST_H */ | ||
diff --git a/src/regress/lib/libcrypto/test/test_util.c b/src/regress/lib/libcrypto/test/test_util.c new file mode 100644 index 0000000000..6ecb574788 --- /dev/null +++ b/src/regress/lib/libcrypto/test/test_util.c | |||
@@ -0,0 +1,51 @@ | |||
1 | /* $OpenBSD: test_util.c,v 1.1 2025/05/21 08:57:13 joshua Exp $ */ | ||
2 | /* | ||
3 | * Copyright (c) 2017 Joel Sing <jsing@openbsd.org> | ||
4 | * Copyright (c) 2024 Theo Buehler <tb@openbsd.org> | ||
5 | * | ||
6 | * Permission to use, copy, modify, and distribute this software for any | ||
7 | * purpose with or without fee is hereby granted, provided that the above | ||
8 | * copyright notice and this permission notice appear in all copies. | ||
9 | * | ||
10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
17 | */ | ||
18 | |||
19 | #include <stdio.h> | ||
20 | #include <stdint.h> | ||
21 | |||
22 | #include "test.h" | ||
23 | |||
24 | void | ||
25 | test_hexdump(struct test *t, const unsigned char *buf, size_t len) | ||
26 | { | ||
27 | size_t i; | ||
28 | |||
29 | for (i = 1; i <= len; i++) | ||
30 | test_printf(t, " 0x%02x,%s", buf[i - 1], i % 8 ? "" : "\n"); | ||
31 | |||
32 | if ((len % 8) != 0) | ||
33 | test_printf(t, "\n"); | ||
34 | } | ||
35 | |||
36 | void | ||
37 | test_hexdiff(struct test *t, const uint8_t *buf, size_t len, const uint8_t *compare) | ||
38 | { | ||
39 | const char *mark = "", *newline; | ||
40 | size_t i; | ||
41 | |||
42 | for (i = 1; i <= len; i++) { | ||
43 | if (compare != NULL) | ||
44 | mark = (buf[i - 1] != compare[i - 1]) ? "*" : " "; | ||
45 | newline = i % 8 ? "" : "\n"; | ||
46 | test_printf(t, " %s0x%02x,%s", mark, buf[i - 1], newline); | ||
47 | } | ||
48 | |||
49 | if ((len % 8) != 0) | ||
50 | test_printf(t, "\n"); | ||
51 | } | ||