Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/plugins/python/regress/testhelpers.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2020 Robert Manner <[email protected]>
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 "testhelpers.h"
20
21
struct TestData data;
22
23
/*
24
* Starting with Python 3.11, backtraces may contain a line with
25
* '~' and '^' characters to bring attention to the important part
26
* of the line.
27
*/
28
static void
29
remove_underline(char *output)
30
{
31
char *cp, *ep;
32
33
// Remove lines that only consist of '~', '^' and white space.
34
cp = output;
35
ep = output + strlen(output);
36
for (;;) {
37
size_t len = strspn(cp, "~^ \t");
38
if (len > 0 && cp[len] == '\n') {
39
/* Prune out lines that are "underlining". */
40
memmove(cp, cp + len + 1, (size_t)(ep - cp));
41
if (*cp == '\0')
42
break;
43
} else {
44
/* No match, move to the next line. */
45
cp = strchr(cp, '\n');
46
if (cp == NULL)
47
break;
48
cp++;
49
}
50
}
51
}
52
53
static void
54
clean_output(char *output)
55
{
56
// we replace some output which otherwise would be test run dependent
57
str_replace_in_place(output, MAX_OUTPUT, data.tmp_dir, TEMP_PATH_TEMPLATE);
58
59
if (data.tmp_dir2)
60
str_replace_in_place(output, MAX_OUTPUT, data.tmp_dir2, TEMP_PATH_TEMPLATE "2");
61
62
str_replace_in_place(output, MAX_OUTPUT, SRC_DIR, "SRC_DIR");
63
64
remove_underline(output);
65
}
66
67
const char *
68
expected_path(const char *format, ...)
69
{
70
static char expected_output_file[PATH_MAX];
71
size_t dirlen = strlcpy(expected_output_file, TESTDATA_DIR, sizeof(expected_output_file));
72
73
va_list args;
74
va_start(args, format);
75
vsnprintf(expected_output_file + dirlen, PATH_MAX - dirlen, format, args);
76
va_end(args);
77
78
return expected_output_file;
79
}
80
81
char **
82
create_str_array(size_t count, ...)
83
{
84
va_list args;
85
86
va_start(args, count);
87
88
char **result = calloc(count, sizeof(char *));
89
if (result != NULL) {
90
for (size_t i = 0; i < count; ++i) {
91
const char *str = va_arg(args, char *);
92
if (str != NULL) {
93
result[i] = strdup(str);
94
if (result[i] == NULL) {
95
while (i > 0) {
96
free(result[--i]);
97
}
98
free(result);
99
result = NULL;
100
break;
101
}
102
}
103
}
104
}
105
106
va_end(args);
107
return result;
108
}
109
110
int
111
is_update(void)
112
{
113
static int result = -1;
114
if (result < 0) {
115
const char *update = getenv("UPDATE_TESTDATA");
116
result = (update && strcmp(update, "1") == 0) ? 1 : 0;
117
}
118
return result;
119
}
120
121
int
122
verify_content(char *actual_content, const char *reference_path)
123
{
124
clean_output(actual_content);
125
126
if (is_update()) {
127
VERIFY_TRUE(fwriteall(reference_path, actual_content));
128
} else {
129
char expected_output[MAX_OUTPUT] = "";
130
if (!freadall(reference_path, expected_output, sizeof(expected_output))) {
131
printf("Error: Missing test data at '%s'\n", reference_path);
132
return false;
133
}
134
VERIFY_STR(actual_content, expected_output);
135
}
136
137
return true;
138
}
139
140
int
141
verify_file(const char *actual_dir, const char *actual_file_name, const char *reference_path)
142
{
143
char actual_path[PATH_MAX];
144
snprintf(actual_path, sizeof(actual_path), "%s/%s", actual_dir, actual_file_name);
145
146
char actual_str[MAX_OUTPUT];
147
if (!freadall(actual_path, actual_str, sizeof(actual_str))) {
148
printf("Expected that file '%s' gets created, but it was not\n", actual_path);
149
return false;
150
}
151
152
int rc = verify_content(actual_str, reference_path);
153
return rc;
154
}
155
156
int
157
fake_conversation(int num_msgs, const struct sudo_conv_message msgs[],
158
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
159
{
160
(void) callback;
161
snprintf_append(data.conv_str, MAX_OUTPUT, "Question count: %d\n", num_msgs);
162
for (int i = 0; i < num_msgs; ++i) {
163
const struct sudo_conv_message *msg = &msgs[i];
164
snprintf_append(data.conv_str, MAX_OUTPUT, "Question %d: <<%s>> (timeout: %d, msg_type=%d)\n",
165
i, msg->msg, msg->timeout, msg->msg_type);
166
167
if (data.conv_replies[i] == NULL)
168
return 1; // simulates user interruption (conversation error)
169
170
replies[i].reply = strdup(data.conv_replies[i]);
171
if (replies[i].reply == NULL)
172
return 1; // memory allocation error
173
}
174
175
return 0; // simulate user answered just fine
176
}
177
178
int
179
fake_conversation_with_suspend(int num_msgs, const struct sudo_conv_message msgs[],
180
struct sudo_conv_reply replies[], struct sudo_conv_callback *callback)
181
{
182
if (callback != NULL) {
183
callback->on_suspend(SIGTSTP, callback->closure);
184
callback->on_resume(SIGCONT, callback->closure);
185
}
186
187
return fake_conversation(num_msgs, msgs, replies, callback);
188
}
189
190
int
191
fake_printf(int msg_type, const char * restrict fmt, ...)
192
{
193
int rc = -1;
194
va_list args;
195
va_start(args, fmt);
196
197
char *output = NULL;
198
switch(msg_type) {
199
case SUDO_CONV_INFO_MSG:
200
output = data.stdout_str;
201
break;
202
case SUDO_CONV_ERROR_MSG:
203
output = data.stderr_str;
204
break;
205
default:
206
break;
207
}
208
209
if (output)
210
rc = vsnprintf_append(output, MAX_OUTPUT, fmt, args);
211
212
va_end(args);
213
return rc;
214
}
215
216
int
217
verify_log_lines(const char *reference_path)
218
{
219
char stored_path[PATH_MAX];
220
snprintf(stored_path, sizeof(stored_path), "%s/%s", data.tmp_dir, "debug.log");
221
222
FILE *file = fopen(stored_path, "rb");
223
if (file == NULL) {
224
printf("Failed to open file '%s'\n", stored_path);
225
return false;
226
}
227
228
char line[1024] = "";
229
char stored_str[MAX_OUTPUT] = "";
230
while (fgets(line, sizeof(line), file) != NULL) {
231
char *line_data = strstr(line, "] "); // this skips the timestamp and pid at the beginning
232
VERIFY_NOT_NULL(line_data); // malformed log line
233
line_data += 2;
234
235
char *line_end = strstr(line_data, " object at "); // this skips checking the pointer hex
236
if (line_end) {
237
snprintf(line_end, sizeof(line) - (size_t)(line_end - line),
238
" object>\n");
239
}
240
241
if (strncmp(line_data, "handle @ /", sizeof("handle @ /") - 1) == 0) {
242
char *start = line_data + sizeof("handle @ ") - 1;
243
244
// normalize path to logging/__init__.py
245
char *logging = strstr(start, "logging/");
246
if (logging != NULL) {
247
memmove(start, logging, strlen(logging) + 1);
248
}
249
250
// remove line number
251
char *colon = strchr(start, ':');
252
if (colon != NULL) {
253
size_t len = strspn(colon + 1, "0123456789");
254
if (len != 0)
255
memmove(colon, colon + len + 1, strlen(colon + len + 1) + 1);
256
}
257
} else if (strncmp(line_data, "LogHandler.emit was called ", 27) == 0) {
258
// LogHandler.emit argument details vary based on python version
259
line_data[26] = '\n';
260
line_data[27] = '\0';
261
} else {
262
// Python 3.11 uses 0 instead of the symbolic REJECT in backtraces
263
char *cp = strstr(line_data, ": REJECT");
264
if (cp != NULL) {
265
// Convert ": REJECT" to ": 0" + rest of line
266
memcpy(cp, ": 0", 3);
267
memmove(cp + 3, cp + 8, strlen(cp + 8) + 1);
268
} else {
269
// Python 3.12 may use <RC.REJECT: 0> instead of 0
270
cp = strstr(line_data, "<RC.REJECT: 0>");
271
if (cp != NULL) {
272
*cp = '0';
273
memmove(cp + 1, cp + 14, strlen(cp + 14) + 1);
274
}
275
}
276
277
}
278
279
VERIFY_TRUE(strlcat(stored_str, line_data, sizeof(stored_str)) < sizeof(stored_str)); // we have enough space in buffer
280
}
281
282
clean_output(stored_str);
283
284
VERIFY_TRUE(verify_content(stored_str, reference_path));
285
return true;
286
}
287
288
int
289
verify_str_set(char **actual_set, char **expected_set, const char *actual_variable_name)
290
{
291
VERIFY_NOT_NULL(actual_set);
292
VERIFY_NOT_NULL(expected_set);
293
294
int actual_len = str_array_count(actual_set);
295
int expected_len = str_array_count(expected_set);
296
297
int matches = false;
298
if (actual_len == expected_len) {
299
int actual_pos = 0;
300
for (; actual_pos < actual_len; ++actual_pos) {
301
char *actual_item = actual_set[actual_pos];
302
303
int expected_pos = 0;
304
for (; expected_pos < expected_len; ++expected_pos) {
305
if (strcmp(actual_item, expected_set[expected_pos]) == 0)
306
break;
307
}
308
309
if (expected_pos == expected_len) {
310
// matching item was not found
311
break;
312
}
313
}
314
315
matches = (actual_pos == actual_len);
316
}
317
318
if (!matches) {
319
char actual_set_str[MAX_OUTPUT] = "";
320
char expected_set_str[MAX_OUTPUT] = "";
321
str_array_snprint(actual_set_str, MAX_OUTPUT, actual_set, actual_len);
322
str_array_snprint(expected_set_str, MAX_OUTPUT, expected_set, expected_len);
323
324
VERIFY_PRINT_MSG("%s", actual_variable_name, actual_set_str, "expected",
325
expected_set_str, "expected to contain the same elements as");
326
return false;
327
}
328
329
return true;
330
}
331
332
int
333
mock_python_datetime_now(const char *plugin_name, const char *date_str)
334
{
335
char *cmd = NULL;
336
int len;
337
len = asprintf(&cmd,
338
"import %s\n" // the plugin has its own submodule
339
"from datetime import datetime\n" // store the real datetime
340
"import time\n"
341
"from unittest.mock import Mock\n"
342
"%s.datetime = Mock()\n" // replace plugin's datetime
343
"%s.datetime.now = lambda: datetime.strptime('%s', '%%Y-%%m-%%dT%%H:%%M:%%S')\n",
344
plugin_name, plugin_name, plugin_name, date_str);
345
if (len == -1)
346
return false;
347
VERIFY_PTR_NE(cmd, NULL);
348
VERIFY_INT(PyRun_SimpleString(cmd), 0);
349
free(cmd);
350
return true;
351
}
352
353