1 /* $NetBSD: utils.c,v 1.3 2014/12/10 04:38:03 christos Exp $ */
2
3 /*
4 * Automated Testing Framework (atf)
5 *
6 * Copyright (c) 2010 The NetBSD Foundation, Inc.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
19 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
20 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
27 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
29 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "atf-c/utils.h"
33
34 #include <sys/stat.h>
35 #include <sys/wait.h>
36
37 #include <err.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <regex.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45
46 #include <atf-c.h>
47
48 #include "detail/dynstr.h"
49
50 /** Searches for a regexp in a string.
51 *
52 * \param regex The regexp to look for.
53 * \param str The string in which to look for the expression.
54 *
55 * \return True if there is a match; false otherwise. */
56 static
57 bool
grep_string(const char * regex,const char * str)58 grep_string(const char *regex, const char *str)
59 {
60 int res;
61 regex_t preg;
62
63 printf("Looking for '%s' in '%s'\n", regex, str);
64 ATF_REQUIRE(regcomp(&preg, regex, REG_EXTENDED) == 0);
65
66 res = regexec(&preg, str, 0, NULL, 0);
67 ATF_REQUIRE(res == 0 || res == REG_NOMATCH);
68
69 regfree(&preg);
70
71 return res == 0;
72 }
73
74 /** Prints the contents of a file to stdout.
75 *
76 * \param name The name of the file to be printed.
77 * \param prefix An string to be prepended to every line of the printed
78 * file. */
79 void
atf_utils_cat_file(const char * name,const char * prefix)80 atf_utils_cat_file(const char *name, const char *prefix)
81 {
82 const int fd = open(name, O_RDONLY);
83 ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
84
85 char buffer[1024];
86 ssize_t count;
87 bool continued = false;
88 while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
89 buffer[count] = '\0';
90
91 if (!continued)
92 printf("%s", prefix);
93
94 char *iter = buffer;
95 char *end;
96 while ((end = strchr(iter, '\n')) != NULL) {
97 *end = '\0';
98 printf("%s\n", iter);
99
100 iter = end + 1;
101 if (iter != buffer + count)
102 printf("%s", prefix);
103 else
104 continued = false;
105 }
106 if (iter < buffer + count) {
107 printf("%s", iter);
108 continued = true;
109 }
110 }
111 ATF_REQUIRE(count == 0);
112 }
113
114 /** Compares a file against the given golden contents.
115 *
116 * \param name Name of the file to be compared.
117 * \param contents Expected contents of the file.
118 *
119 * \return True if the file matches the contents; false otherwise. */
120 bool
atf_utils_compare_file(const char * name,const char * contents)121 atf_utils_compare_file(const char *name, const char *contents)
122 {
123 const int fd = open(name, O_RDONLY);
124 ATF_REQUIRE_MSG(fd != -1, "Cannot open %s", name);
125
126 const char *pos = contents;
127 ssize_t remaining = strlen(contents);
128
129 char buffer[1024];
130 ssize_t count;
131 while ((count = read(fd, buffer, sizeof(buffer))) > 0 &&
132 count <= remaining) {
133 if (memcmp(pos, buffer, count) != 0) {
134 close(fd);
135 return false;
136 }
137 remaining -= count;
138 pos += count;
139 }
140 close(fd);
141 return count == 0 && remaining == 0;
142 }
143
144 /** Copies a file.
145 *
146 * \param source Path to the source file.
147 * \param destination Path to the destination file. */
148 void
atf_utils_copy_file(const char * source,const char * destination)149 atf_utils_copy_file(const char *source, const char *destination)
150 {
151 const int input = open(source, O_RDONLY);
152 ATF_REQUIRE_MSG(input != -1, "Failed to open source file during "
153 "copy (%s)", source);
154
155 const int output = open(destination, O_WRONLY | O_CREAT | O_TRUNC, 0777);
156 ATF_REQUIRE_MSG(output != -1, "Failed to open destination file during "
157 "copy (%s)", destination);
158
159 char buffer[1024];
160 ssize_t length;
161 while ((length = read(input, buffer, sizeof(buffer))) > 0)
162 ATF_REQUIRE_MSG(write(output, buffer, length) == length,
163 "Failed to write to %s during copy", destination);
164 ATF_REQUIRE_MSG(length != -1, "Failed to read from %s during copy", source);
165
166 struct stat sb;
167 ATF_REQUIRE_MSG(fstat(input, &sb) != -1,
168 "Failed to stat source file %s during copy", source);
169 ATF_REQUIRE_MSG(fchmod(output, sb.st_mode) != -1,
170 "Failed to chmod destination file %s during copy",
171 destination);
172
173 close(output);
174 close(input);
175 }
176
177 /** Creates a file.
178 *
179 * \param name Name of the file to create.
180 * \param contents Text to write into the created file.
181 * \param ... Positional parameters to the contents. */
182 void
atf_utils_create_file(const char * name,const char * contents,...)183 atf_utils_create_file(const char *name, const char *contents, ...)
184 {
185 va_list ap;
186 atf_dynstr_t formatted;
187 atf_error_t error;
188
189 va_start(ap, contents);
190 error = atf_dynstr_init_ap(&formatted, contents, ap);
191 va_end(ap);
192 ATF_REQUIRE(!atf_is_error(error));
193
194 const int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
195 ATF_REQUIRE_MSG(fd != -1, "Cannot create file %s", name);
196 ATF_REQUIRE(write(fd, atf_dynstr_cstring(&formatted),
197 atf_dynstr_length(&formatted)) != -1);
198 close(fd);
199
200 atf_dynstr_fini(&formatted);
201 }
202
203 /** Checks if a file exists.
204 *
205 * \param path Location of the file to check for.
206 *
207 * \return True if the file exists, false otherwise. */
208 bool
atf_utils_file_exists(const char * path)209 atf_utils_file_exists(const char *path)
210 {
211 const int ret = access(path, F_OK);
212 if (ret == -1) {
213 if (errno != ENOENT)
214 atf_tc_fail("Failed to check the existence of %s: %s", path,
215 strerror(errno));
216 else
217 return false;
218 } else
219 return true;
220 }
221
222 /** Spawns a subprocess and redirects its output to files.
223 *
224 * Use the atf_utils_wait() function to wait for the completion of the spawned
225 * subprocess and validate its exit conditions.
226 *
227 * \return 0 in the new child; the PID of the new child in the parent. Does
228 * not return in error conditions. */
229 pid_t
atf_utils_fork(void)230 atf_utils_fork(void)
231 {
232 const pid_t pid = fork();
233 if (pid == -1)
234 atf_tc_fail("fork failed");
235
236 if (pid == 0) {
237 atf_utils_redirect(STDOUT_FILENO, "atf_utils_fork_out.txt");
238 atf_utils_redirect(STDERR_FILENO, "atf_utils_fork_err.txt");
239 }
240 return pid;
241 }
242
243 /** Frees an dynamically-allocated "argv" array.
244 *
245 * \param argv A dynamically-allocated array of dynamically-allocated
246 * strings. */
247 void
atf_utils_free_charpp(char ** argv)248 atf_utils_free_charpp(char **argv)
249 {
250 char **ptr;
251
252 for (ptr = argv; *ptr != NULL; ptr++)
253 free(*ptr);
254
255 free(argv);
256 }
257
258 /** Searches for a regexp in a file.
259 *
260 * \param regex The regexp to look for.
261 * \param file The file in which to look for the expression.
262 * \param ... Positional parameters to the regex.
263 *
264 * \return True if there is a match; false otherwise. */
265 bool
atf_utils_grep_file(const char * regex,const char * file,...)266 atf_utils_grep_file(const char *regex, const char *file, ...)
267 {
268 int fd;
269 va_list ap;
270 atf_dynstr_t formatted;
271 atf_error_t error;
272
273 va_start(ap, file);
274 error = atf_dynstr_init_ap(&formatted, regex, ap);
275 va_end(ap);
276 ATF_REQUIRE(!atf_is_error(error));
277
278 ATF_REQUIRE((fd = open(file, O_RDONLY)) != -1);
279 bool found = false;
280 char *line = NULL;
281 while (!found && (line = atf_utils_readline(fd)) != NULL) {
282 found = grep_string(atf_dynstr_cstring(&formatted), line);
283 free(line);
284 }
285 close(fd);
286
287 atf_dynstr_fini(&formatted);
288
289 return found;
290 }
291
292 /** Searches for a regexp in a string.
293 *
294 * \param regex The regexp to look for.
295 * \param str The string in which to look for the expression.
296 * \param ... Positional parameters to the regex.
297 *
298 * \return True if there is a match; false otherwise. */
299 bool
atf_utils_grep_string(const char * regex,const char * str,...)300 atf_utils_grep_string(const char *regex, const char *str, ...)
301 {
302 bool res;
303 va_list ap;
304 atf_dynstr_t formatted;
305 atf_error_t error;
306
307 va_start(ap, str);
308 error = atf_dynstr_init_ap(&formatted, regex, ap);
309 va_end(ap);
310 ATF_REQUIRE(!atf_is_error(error));
311
312 res = grep_string(atf_dynstr_cstring(&formatted), str);
313
314 atf_dynstr_fini(&formatted);
315
316 return res;
317 }
318
319 /** Reads a line of arbitrary length.
320 *
321 * \param fd The descriptor from which to read the line.
322 *
323 * \return A pointer to the read line, which must be released with free(), or
324 * NULL if there was nothing to read from the file. */
325 char *
atf_utils_readline(const int fd)326 atf_utils_readline(const int fd)
327 {
328 char ch;
329 ssize_t cnt;
330 atf_dynstr_t temp;
331 atf_error_t error;
332
333 error = atf_dynstr_init(&temp);
334 ATF_REQUIRE(!atf_is_error(error));
335
336 while ((cnt = read(fd, &ch, sizeof(ch))) == sizeof(ch) &&
337 ch != '\n') {
338 error = atf_dynstr_append_fmt(&temp, "%c", ch);
339 ATF_REQUIRE(!atf_is_error(error));
340 }
341 ATF_REQUIRE(cnt != -1);
342
343 if (cnt == 0 && atf_dynstr_length(&temp) == 0) {
344 atf_dynstr_fini(&temp);
345 return NULL;
346 } else
347 return atf_dynstr_fini_disown(&temp);
348 }
349
350 /** Redirects a file descriptor to a file.
351 *
352 * \param target_fd The file descriptor to be replaced.
353 * \param name The name of the file to direct the descriptor to.
354 *
355 * \pre Should only be called from the process spawned by fork_for_testing
356 * because this exits uncontrolledly.
357 * \post Terminates execution if the redirection fails. */
358 void
atf_utils_redirect(const int target_fd,const char * name)359 atf_utils_redirect(const int target_fd, const char *name)
360 {
361 if (target_fd == STDOUT_FILENO)
362 fflush(stdout);
363 else if (target_fd == STDERR_FILENO)
364 fflush(stderr);
365
366 const int new_fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
367 if (new_fd == -1)
368 err(EXIT_FAILURE, "Cannot create %s", name);
369 if (new_fd != target_fd) {
370 if (dup2(new_fd, target_fd) == -1)
371 err(EXIT_FAILURE, "Cannot redirect to fd %d", target_fd);
372 }
373 close(new_fd);
374 }
375
376 /** Waits for a subprocess and validates its exit condition.
377 *
378 * \param pid The process to be waited for. Must have been started by
379 * testutils_fork().
380 * \param exitstatus Expected exit status.
381 * \param expout Expected contents of stdout.
382 * \param experr Expected contents of stderr. */
383 void
atf_utils_wait(const pid_t pid,const int exitstatus,const char * expout,const char * experr)384 atf_utils_wait(const pid_t pid, const int exitstatus, const char *expout,
385 const char *experr)
386 {
387 int status;
388 ATF_REQUIRE(waitpid(pid, &status, 0) != -1);
389
390 atf_utils_cat_file("atf_utils_fork_out.txt", "subprocess stdout: ");
391 atf_utils_cat_file("atf_utils_fork_err.txt", "subprocess stderr: ");
392
393 ATF_REQUIRE(WIFEXITED(status));
394 ATF_REQUIRE_EQ(exitstatus, WEXITSTATUS(status));
395
396 const char *save_prefix = "save:";
397 const size_t save_prefix_length = strlen(save_prefix);
398
399 if (strlen(expout) > save_prefix_length &&
400 strncmp(expout, save_prefix, save_prefix_length) == 0) {
401 atf_utils_copy_file("atf_utils_fork_out.txt",
402 expout + save_prefix_length);
403 } else {
404 ATF_REQUIRE(atf_utils_compare_file("atf_utils_fork_out.txt", expout));
405 }
406
407 if (strlen(experr) > save_prefix_length &&
408 strncmp(experr, save_prefix, save_prefix_length) == 0) {
409 atf_utils_copy_file("atf_utils_fork_err.txt",
410 experr + save_prefix_length);
411 } else {
412 ATF_REQUIRE(atf_utils_compare_file("atf_utils_fork_err.txt", experr));
413 }
414
415 ATF_REQUIRE(unlink("atf_utils_fork_out.txt") != -1);
416 ATF_REQUIRE(unlink("atf_utils_fork_err.txt") != -1);
417 }
418