Home | History | Annotate | Line # | Download | only in dist
      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*
     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
     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
     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*
    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
    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
    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*
    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
    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 = NULL; char* value = NULL;
    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
    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 = NULL; char* value = NULL;
    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
    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