1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <libgen.h>
6#include <limits.h>
7#include <stdlib.h>
8#include <unittest/unittest.h>
9
10#include "watchdog.h"
11
12static test_case_element* test_case_list = nullptr;
13static test_case_element* failed_test_case_list = nullptr;
14
15static unittest_help_printer_type* print_test_help = nullptr;
16
17// Registers a test case with the unit test framework.
18void unittest_register_test_case(test_case_element* elem) {
19    elem->next = test_case_list;
20    test_case_list = elem;
21}
22
23bool unittest_run_one_test(test_case_element* elem, test_type_t type) {
24    utest_test_type = type;
25    return elem->test_case(false, nullptr);
26}
27
28void unittest_register_test_help_printer(unittest_help_printer_type* func) {
29    print_test_help = func;
30}
31
32// Case name and test name are optional parameters that will cause only the
33// test[case]s matching the given name to run. If null, all test[case]s will
34// run.
35static bool unittest_run_all_tests_etc(const char* test_binary_name, test_type_t type,
36                                       const char* case_name, const char* test_name,
37                                       bool list_only) {
38    unsigned int n_tests = 0;
39    unsigned int n_failed = 0;
40
41    utest_test_type = type;
42
43    test_case_element* current = test_case_list;
44    while (current) {
45        if (!case_name || strcmp(current->name, case_name) == 0) {
46            if (!current->test_case(list_only, test_name)) {
47                current->failed_next = failed_test_case_list;
48                failed_test_case_list = current;
49                n_failed++;
50            }
51            n_tests++;
52        }
53        current = current->next;
54    }
55
56    // Don't print test results in list mode.
57    if (list_only)
58        return true;
59
60    unittest_printf_critical("====================================================\n");
61    if (test_binary_name != nullptr && test_binary_name[0] != '\0') {
62        unittest_printf_critical("Results for test binary \"%s\":\n", test_binary_name);
63    } else {
64        // argv[0] can be null for binaries that run as userboot,
65        // like core-tests.
66        unittest_printf_critical("Results:\n");
67    }
68    if (n_failed == 0) {
69        unittest_printf_critical("    SUCCESS!  All test cases passed!\n");
70    } else {
71        unittest_printf_critical("\n");
72        unittest_printf_critical("    The following test cases failed:\n");
73        test_case_element* failed = failed_test_case_list;
74        while (failed) {
75            unittest_printf_critical("        %s\n", failed->name);
76            test_case_element* failed_next = failed->failed_next;
77            failed->failed_next = nullptr;
78            failed = failed_next;
79        }
80        failed_test_case_list = nullptr;
81        unittest_printf_critical("\n");
82    }
83    unittest_printf_critical("    CASES:  %d     SUCCESS:  %d     FAILED:  %d   \n", n_tests,
84                             n_tests - n_failed, n_failed);
85    unittest_printf_critical("====================================================\n");
86    return n_failed == 0;
87}
88
89static void print_help(const char* prog_name, FILE* f) {
90    fprintf(f, "Usage: %s [OPTIONS]\n", prog_name);
91    fprintf(f, "\nOptions:\n"
92            "  -h | --help\n"
93            "      Prints this text and exits.\n"
94            "\n"
95            "  --list\n"
96            "      Prints the test names instead of running them.\n"
97            "\n"
98            "  --case <test_case>\n"
99            "      Only the tests from the matching test case will be run.\n"
100            "      <test_case> is case-sensitive; regex is not supported\n"
101            "\n"
102            "  --test <test>\n"
103            "      Only the tests from the matching test will be run\n"
104            "      <test> is case-sensitive; regex is not supported\n"
105            "\n"
106            "  v=<level>\n"
107            "      Set the unit test verbosity level to <level>\n"
108            );
109    if (print_test_help) {
110        fprintf(f, "\nTest-specific options:\n");
111        print_test_help(f);
112    }
113    fprintf(f, "\n"
114            "Environment variables:\n"
115            "  %s=<types-mask>\n"
116            "      Specifies the types of tests to run.\n"
117            "      Must be the OR of the following values, in base 10:\n"
118            "        0x01 = small\n"
119            "        0x02 = medium\n"
120            "        0x04 = large\n"
121            "        0x08 = performance\n"
122            "      If unspecified then all tests are run.\n"
123            "\n"
124            "  %s=<base-timeout-in-seconds>\n"
125            "      Specifies the base timeout which is the timeout of\n"
126            "      small tests. Other test types have a timeout that is a\n"
127            "      multiple of this amount. If unspecified the default base\n"
128            "      timeout is %d seconds.\n",
129            TEST_ENV_NAME, WATCHDOG_ENV_NAME,
130            DEFAULT_BASE_TIMEOUT_SECONDS);
131    fprintf(f,
132            "      A scaling factor is applied to the base timeout:\n"
133            "        Small       - x %d\n"
134            "        Medium      - x %d\n"
135            "        Large       - x %d\n"
136            "        Performance - x %d\n",
137            TEST_TIMEOUT_FACTOR_SMALL, TEST_TIMEOUT_FACTOR_MEDIUM,
138            TEST_TIMEOUT_FACTOR_LARGE, TEST_TIMEOUT_FACTOR_PERFORMANCE);
139}
140
141/*
142 * Runs all registered test cases.
143 */
144bool unittest_run_all_tests(int argc, char** argv) {
145    const char* prog_name = basename(argv[0]);
146    bool list_tests_only = false;
147    const char* case_matcher = nullptr;
148    const char* test_matcher = nullptr;
149
150    int i = 1;
151    while (i < argc) {
152        const char* arg = argv[i];
153        if (arg[0] == '-') {
154            // Got a switch.
155            if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
156                // Specifying --help at any point prints the help and exits.
157                print_help(prog_name, stdout);
158                return true;
159            } else if (strcmp(arg, "--list") == 0) {
160                list_tests_only = true;
161            } else if (strcmp(arg, "--case") == 0) {
162                if (i + 1 >= argc) {
163                    fprintf(stderr, "Error: missing arg to %s\n", arg);
164                    return false;
165                }
166                case_matcher = argv[++i];
167            } else if (strcmp(arg, "--test") == 0) {
168                if (i + 1 >= argc) {
169                    fprintf(stderr, "Error: missing arg to %s\n", arg);
170                    return false;
171                }
172                test_matcher = argv[++i];
173            }
174        } else if ((strlen(arg) == 3) && (arg[0] == 'v') && (arg[1] == '=')) {
175            unittest_set_verbosity_level(arg[2] - '0');
176        } // Ignore other parameters
177        i++;
178    }
179
180    // Rely on the TEST_ENV_NAME environment variable to tell us which
181    // classes of tests we should execute.
182    const char* test_type_str = getenv(TEST_ENV_NAME);
183    test_type_t test_type;
184    if (test_type_str == nullptr) {
185        // If we cannot access the environment variable, run all tests
186        test_type = TEST_ALL;
187    } else {
188        test_type = static_cast<test_type_t>(atoi(test_type_str));
189    }
190
191    // Rely on the WATCHDOG_ENV_NAME environment variable to tell us
192    // the timeout to use.
193    const char* watchdog_timeout_str = getenv(WATCHDOG_ENV_NAME);
194    if (watchdog_timeout_str != nullptr) {
195        char* end;
196        long timeout = strtol(watchdog_timeout_str, &end, 0);
197        if (*watchdog_timeout_str == '\0' || *end != '\0' ||
198            timeout < 0 || timeout > INT_MAX) {
199            fprintf(stderr, "Error: bad watchdog timeout\n");
200            return false;
201        }
202        watchdog_set_base_timeout(static_cast<int>(timeout));
203    }
204
205    watchdog_initialize();
206
207    auto result = unittest_run_all_tests_etc(argv[0], test_type, case_matcher, test_matcher,
208                                             list_tests_only);
209
210    watchdog_terminate();
211    return result;
212}
213