Home | History | Annotate | Line # | Download | only in dist
      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