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