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