xref: /netbsd-src/external/bsd/kyua-testers/dist/atf_list.c (revision 8faee98977258cf9a6a22d27c0e1864a8fa6e38b)
1 // Copyright 2012 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "atf_list.h"
30 
31 #include <assert.h>
32 #include <errno.h>
33 #include <stdarg.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 
39 #include "error.h"
40 
41 
42 /// Expected header in the test program list.
43 #define TP_LIST_HEADER "Content-Type: application/X-atf-tp; version=\"1\""
44 
45 
46 /// Same as fgets, but removes any trailing newline from the output string.
47 ///
48 /// \param [out] str Pointer to the output buffer.
49 /// \param size Length of the output buffer.
50 /// \param [in,out] stream File from which to read the line.
51 ///
52 /// \return A pointer to the output buffer if successful; otherwise NULL.
53 static char*
fgets_no_newline(char * str,int size,FILE * stream)54 fgets_no_newline(char* str, int size, FILE* stream)
55 {
56     char* result = fgets(str, size, stream);
57     if (result != NULL) {
58         const size_t length = strlen(str);
59         if (length > 0 && str[length - 1] == '\n')
60             str[length - 1] = '\0';
61     }
62     return result;
63 }
64 
65 
66 /// Generates an error for the case where fgets() returns NULL.
67 ///
68 /// \param input Stream on which fgets() returned an error.
69 /// \param message Error message.
70 ///
71 /// \return An error object with the error message and any relevant details.
72 static kyua_error_t
fgets_error(FILE * input,const char * message)73 fgets_error(FILE* input, const char* message)
74 {
75     if (feof(input)) {
76         return kyua_generic_error_new("%s: unexpected EOF", message);
77     } else {
78         assert(ferror(input));
79         return kyua_libc_error_new(errno, "%s", message);
80     }
81 }
82 
83 
84 /// Reads the header of the test cases list.
85 ///
86 /// The header does not carry any useful information, so all this function does
87 /// is ensure the header is valid.
88 ///
89 /// \param [in,out] input File from which to read the header.
90 ///
91 /// \return OK if the header is valid; an error if it is not.
92 static kyua_error_t
parse_header(FILE * input)93 parse_header(FILE* input)
94 {
95     char line[80];  // It's ugly to have a limit, but it's easier this way.
96 
97     if (fgets_no_newline(line, sizeof(line), input) == NULL)
98         return fgets_error(input, "fgets failed to read test cases list "
99                            "header");
100     if (strcmp(line, TP_LIST_HEADER) != 0)
101         return kyua_generic_error_new("Invalid test cases list header '%s'",
102                                       line);
103 
104     if (fgets_no_newline(line, sizeof(line), input) == NULL)
105         return fgets_error(input, "fgets failed to read test cases list "
106                            "header");
107     if (strcmp(line, "") != 0)
108         return kyua_generic_error_new("Incomplete test cases list header");
109 
110     return kyua_error_ok();
111 }
112 
113 
114 /// Looks for the first occurrence of any of the specified delimiters.
115 ///
116 /// \param container String in which to look for the delimiters.
117 /// \param delimiters List of delimiters to look for.
118 ///
119 /// \return A pointer to the first occurrence of the delimiter, or NULL if
120 /// there is none.
121 static char*
find_first_of(char * container,const char * delimiters)122 find_first_of(char* container, const char* delimiters)
123 {
124     char* ptr = container;
125     while (*ptr != '\0') {
126         if (strchr(delimiters, *ptr) != NULL)
127             return ptr;
128         ++ptr;
129     }
130     return NULL;
131 }
132 
133 
134 /// Prints a string within single quotes, with proper escaping.
135 ///
136 /// \param [in,out] line The line to be printed.  This is a non-const pointer
137 ///     and the input string is modified to simplify tokenization.
138 /// \param [in,out] output Buffer onto which to write the quoted string.
139 /// \param surrounding If true, surround the printed value with single quotes.
140 static void
print_quoted(char * line,FILE * output,const bool surrounding)141 print_quoted(char* line, FILE* output, const bool surrounding)
142 {
143     if (surrounding)
144         fprintf(output, "'");
145 
146     char* quoteptr;
147     while ((quoteptr = find_first_of(line, "\'\\")) != NULL) {
148         const char quote = *quoteptr;
149         *quoteptr = '\0';
150         fprintf(output, "%s\\%c", line, quote);
151         line = quoteptr + 1;
152     }
153 
154     if (surrounding)
155         fprintf(output, "%s'", line);
156     else
157         fprintf(output, "%s", line);
158 }
159 
160 
161 /// Parses a property from the test cases list.
162 ///
163 /// The property is of the form "name: value", where the value extends to the
164 /// end of the line without quotations.
165 ///
166 /// \param [in,out] line The line to be parsed.  This is a non-const pointer
167 ///     and the input string is modified to simplify tokenization.
168 /// \param [out] key The name of the property if the parsing succeeds.  This
169 ///     is a pointer within the input line.
170 /// \param [out] value The value of the property if the parsing succeeds.  This
171 ///     is a pointer within the input line.
172 ///
173 /// \return OK if the line contains a valid property; an error otherwise.
174 /// In case of success, both key and value are updated.
175 static kyua_error_t
parse_property(char * line,char ** const key,char ** const value)176 parse_property(char* line, char** const key, char** const value)
177 {
178     char* delim = strstr(line, ": ");
179     if (delim == NULL)
180         return kyua_generic_error_new("Invalid property '%s'", line);
181     *delim = '\0'; *(delim + 1) = '\0';
182 
183     *key = line;
184     *value = delim + 2;
185     return kyua_error_ok();
186 }
187 
188 
189 /// Static value to denote an error in the return of rewrite_property;
190 static const char* rewrite_error = "ERROR";
191 
192 
193 /// Converts the name of an ATF property to a Kyua generic property.
194 ///
195 /// \param name The name of the ATF property to process.
196 ///
197 /// \return The name of the corresponding Kyua property if the input property is
198 /// valid; NULL if the property has a custom name that has to be handled in the
199 /// parent; or rewrite_error if the property is invalid.  If this returns
200 /// rewrite_error, it's OK to pointer-compare the return value to the static
201 /// symbol for equality.
202 static const char*
rewrite_property(const char * name)203 rewrite_property(const char* name)
204 {
205     if (strcmp(name, "descr") == 0)
206         return "description";
207     else if (strcmp(name, "has.cleanup") == 0)
208         return "has_cleanup";
209     else if (strcmp(name, "require.arch") == 0)
210         return "allowed_architectures";
211     else if (strcmp(name, "require.config") == 0)
212         return "required_configs";
213     else if (strcmp(name, "require.files") == 0)
214         return "required_files";
215     else if (strcmp(name, "require.machine") == 0)
216         return "allowed_platforms";
217     else if (strcmp(name, "require.memory") == 0)
218         return "required_memory";
219     else if (strcmp(name, "require.progs") == 0)
220         return "required_programs";
221     else if (strcmp(name, "require.user") == 0)
222         return "required_user";
223     else if (strcmp(name, "timeout") == 0)
224         return "timeout";
225     else if (strlen(name) > 2 && name[0] == 'X' && name[1] == '-')
226         return NULL;
227     else
228         return rewrite_error;
229 }
230 
231 
232 /// Parses a single test case and writes it to the output.
233 ///
234 /// This has to be called after the ident property has been read, and takes care
235 /// of reading the rest of the test case and printing the parsed result.
236 ///
237 /// Be aware that this consumes the newline after the test case.  The caller
238 /// should not look for it.
239 ///
240 /// \param [in,out] input File from which to read the header.
241 /// \param [in,out] output File to which to write the parsed test case.
242 /// \param [in,out] name The name of the test case.  This is a non-const pointer
243 ///     and the input string is modified to simplify tokenization.
244 ///
245 /// \return OK if the parsing succeeds; an error otherwise.
246 static kyua_error_t
parse_test_case(FILE * input,FILE * output,char * name)247 parse_test_case(FILE* input, FILE* output, char* name)
248 {
249     kyua_error_t error;
250     char line[1024];  // It's ugly to have a limit, but it's easier this way.
251 
252     fprintf(output, "test_case{name=");
253     print_quoted(name, output, true);
254 
255     error = kyua_error_ok();
256     while (!kyua_error_is_set(error) &&
257            fgets_no_newline(line, sizeof(line), input) != NULL &&
258            strcmp(line, "") != 0) {
259         char* key; char* value;
260         error = parse_property(line, &key, &value);
261         if (!kyua_error_is_set(error)) {
262             const char* out_key = rewrite_property(key);
263             if (out_key == rewrite_error) {
264                 error = kyua_generic_error_new("Unknown ATF property %s", key);
265             } else if (out_key == NULL) {
266                 fprintf(output, ", ['custom.");
267                 print_quoted(key, output, false);
268                 fprintf(output, "']=");
269                 print_quoted(value, output, true);
270             } else {
271                 fprintf(output, ", %s=", out_key);
272                 print_quoted(value, output, true);
273             }
274         }
275     }
276 
277     fprintf(output, "}\n");
278 
279     return error;
280 }
281 
282 
283 /// Rewrites the test cases list from the input to the output.
284 ///
285 /// \param [in,out] input Stream from which to read the test program's test
286 ///     cases list.  The current location must be after the header and at the
287 ///     first identifier (if any).
288 /// \param [out] output Stream to which to write the generic list.
289 ///
290 /// \return An error object.
291 static kyua_error_t
parse_tests(FILE * input,FILE * output)292 parse_tests(FILE* input, FILE* output)
293 {
294     char line[512];  // It's ugly to have a limit, but it's easier this way.
295 
296     if (fgets_no_newline(line, sizeof(line), input) == NULL) {
297         return fgets_error(input, "Empty test cases list");
298     }
299 
300     kyua_error_t error;
301 
302     do {
303         char* key; char* value;
304         error = parse_property(line, &key, &value);
305         if (kyua_error_is_set(error))
306             break;
307 
308         if (strcmp(key, "ident") == 0) {
309             error = parse_test_case(input, output, value);
310         } else {
311             error = kyua_generic_error_new("Expected ident property, got %s",
312                                            key);
313         }
314     } while (!kyua_error_is_set(error) &&
315              fgets_no_newline(line, sizeof(line), input) != NULL);
316 
317     if (!kyua_error_is_set(error)) {
318         if (ferror(input))
319             error = kyua_libc_error_new(errno, "fgets failed");
320         else
321             assert(feof(input));
322     }
323 
324     return error;
325 }
326 
327 
328 /// Reads an ATF test cases list and prints a Kyua definition.
329 ///
330 /// \param fd A file descriptor from which to read the test cases list of a test
331 ///     program.  Should be connected to the stdout of the latter.  This
332 ///     function grabs ownership of the descriptor and releases it in all cases.
333 /// \param [in,out] output File to which to write the Kyua definition.
334 ///
335 /// \return OK if the parsing succeeds; an error otherwise.  Note that, if there
336 /// is an error, the output may not be consistent and should not be used.
337 kyua_error_t
atf_list_parse(const int fd,FILE * output)338 atf_list_parse(const int fd, FILE* output)
339 {
340     kyua_error_t error;
341 
342     FILE* input = fdopen(fd, "r");
343     if (input == NULL) {
344         error = kyua_libc_error_new(errno, "fdopen(%d) failed", fd);
345         close(fd);
346     } else {
347         error = parse_header(input);
348         if (!kyua_error_is_set(error)) {
349             error = parse_tests(input, output);
350         }
351         fclose(input);
352     }
353 
354     return error;
355 }
356