1// Copyright 2016 The Fuchsia Authors 2// Copyright (c) 2013, Google, Inc. All rights reserved 3// 4// Use of this source code is governed by a MIT-style 5// license that can be found in the LICENSE file or at 6// https://opensource.org/licenses/MIT 7 8/* 9 * Functions for unit tests. See lib/unittest/include/unittest.h for usage. 10 */ 11#include <assert.h> 12#include <debug.h> 13#include <err.h> 14#include <fbl/auto_call.h> 15#include <inttypes.h> 16#include <kernel/mutex.h> 17#include <kernel/thread.h> 18#include <lib/unittest/unittest.h> 19#include <platform.h> 20#include <stdbool.h> 21#include <stddef.h> 22#include <stdint.h> 23#include <stdio.h> 24#include <stdlib.h> 25#include <string.h> 26#include <vm/vm_aspace.h> 27#include <zircon/compiler.h> 28#include <zircon/types.h> 29 30// Ensures unittests are not run concurrently. 31static mutex_t lock = MUTEX_INITIAL_VALUE(lock); 32 33/** 34 * \brief Function called to dump results 35 * 36 * This function will call the out_func callback 37 */ 38int unittest_printf(const char* format, ...) { 39 int ret = 0; 40 41 va_list argp; 42 va_start(argp, format); 43 ret = vprintf(format, argp); 44 va_end(argp); 45 46 return ret; 47} 48 49bool unittest_expect_bytes(const uint8_t* expected, 50 const char* expected_name, 51 const uint8_t* actual, 52 const char* actual_name, 53 size_t len, 54 const char* msg, 55 const char* func, 56 int line, 57 bool expect_eq) { 58 if (!memcmp(expected, actual, len) != expect_eq) { 59 60 unittest_printf(UNITTEST_FAIL_TRACEF_FORMAT "%s:\n%s %s %s, but %s!\n", 61 func, line, msg, 62 expected_name, 63 expect_eq ? "does not match" : "matches", 64 actual_name, 65 expect_eq ? "should" : "should not"); 66 67 unittest_printf("expected (%s)\n", expected_name); 68 hexdump8_very_ex(expected, len, (uint64_t)((addr_t)expected), unittest_printf); 69 unittest_printf("actual (%s)\n", actual_name); 70 hexdump8_very_ex(actual, len, (uint64_t)((addr_t)actual), unittest_printf); 71 72 return false; 73 } 74 return true; 75} 76 77#include <lib/console.h> 78 79// External references to the testcase registration tables. 80extern unittest_testcase_registration_t __start_unittest_testcases[]; 81extern unittest_testcase_registration_t __stop_unittest_testcases[]; 82 83static void usage(const char* progname) { 84 printf("Usage:\n" 85 "%s <case>\n" 86 " where case is a specific testcase name, or...\n" 87 " all : run all tests\n" 88 " ? : list tests\n", 89 progname); 90} 91 92static void list_cases(void) { 93 size_t count = 0; 94 size_t max_namelen = 0; 95 96 const unittest_testcase_registration_t* testcase; 97 for (testcase = __start_unittest_testcases; 98 testcase != __stop_unittest_testcases; 99 ++testcase) { 100 101 if (testcase->name) { 102 size_t namelen = strlen(testcase->name); 103 if (max_namelen < namelen) 104 max_namelen = namelen; 105 count++; 106 } 107 } 108 109 printf("There %s %zu test case%s available...\n", 110 count == 1 ? "is" : "are", 111 count, 112 count == 1 ? "" : "s"); 113 114 for (testcase = __start_unittest_testcases; 115 testcase != __stop_unittest_testcases; 116 ++testcase) { 117 118 if (testcase->name) 119 printf(" %-*s : %s\n", 120 static_cast<int>(max_namelen), testcase->name, 121 testcase->desc ? testcase->desc : "<no description>"); 122 } 123} 124 125static bool run_unittest(const unittest_testcase_registration_t* testcase) { 126 size_t max_namelen = 0; 127 size_t passed = 0; 128 129 DEBUG_ASSERT(testcase); 130 DEBUG_ASSERT(testcase->name); 131 DEBUG_ASSERT(!!testcase->tests == !!testcase->test_cnt); 132 133 for (size_t i = 0; i < testcase->test_cnt; ++i) { 134 const unittest_registration_t* test = &testcase->tests[i]; 135 if (test->name) { 136 size_t namelen = strlen(test->name); 137 if (max_namelen < namelen) 138 max_namelen = namelen; 139 } 140 } 141 142 unittest_printf("%s : Running %zu test%s...\n", 143 testcase->name, 144 testcase->test_cnt, 145 testcase->test_cnt == 1 ? "" : "s"); 146 147 zx_time_t testcase_start = current_time(); 148 149 for (size_t i = 0; i < testcase->test_cnt; ++i) { 150 const unittest_registration_t* test = &testcase->tests[i]; 151 152 printf(" %-*s : ", 153 static_cast<int>(max_namelen), test->name ? test->name : ""); 154 155 zx_time_t test_start = current_time(); 156 bool good = test->fn ? test->fn() : false; 157 zx_duration_t test_runtime = current_time() - test_start; 158 159 if (good) { 160 passed++; 161 } else { 162 printf(" %-*s : ", 163 static_cast<int>(max_namelen), test->name ? test->name : ""); 164 } 165 166 unittest_printf("%s (%" PRIu64 " nSec)\n", 167 good ? "PASSED" : "FAILED", 168 test_runtime); 169 } 170 171 zx_duration_t testcase_runtime = current_time() - testcase_start; 172 173 unittest_printf("%s : %sll tests passed (%zu/%zu) in %" PRIu64 " nSec\n", 174 testcase->name, 175 passed != testcase->test_cnt ? "Not a" : "A", 176 passed, testcase->test_cnt, 177 testcase_runtime); 178 179 return passed == testcase->test_cnt; 180} 181 182// Runs the testcase specified by |arg| and returns 1 if test passes. 183// 184// |arg| is a const unittest_testcase_registration_t*. 185static int run_unittest_thread_entry(void* arg) { 186 auto* testcase = static_cast<const unittest_testcase_registration_t*>(arg); 187 return run_unittest(testcase); 188} 189 190// Runs |testcase| in another thread and waits for it to complete. 191// 192// Returns true if the test passed. 193static bool run_testcase_in_thread(const unittest_testcase_registration_t* testcase) { 194 fbl::RefPtr<VmAspace> aspace = VmAspace::Create(VmAspace::TYPE_USER, "unittest"); 195 if (!aspace) { 196 unittest_printf("failed to create unittest user aspace\n"); 197 return false; 198 } 199 auto destroy_aspace = fbl::MakeAutoCall([&]() { 200 zx_status_t status = aspace->Destroy(); 201 DEBUG_ASSERT(status == ZX_OK); 202 }); 203 thread_t* t = thread_create("unittest", run_unittest_thread_entry, 204 const_cast<void*>(static_cast<const void*>(testcase)), 205 DEFAULT_PRIORITY); 206 if (!t) { 207 unittest_printf("failed to create unittest thread\n"); 208 return false; 209 } 210 aspace->AttachToThread(t); 211 212 thread_resume(t); 213 int success = 0; 214 zx_status_t status = thread_join(t, &success, ZX_TIME_INFINITE); 215 if (status != ZX_OK) { 216 unittest_printf("failed to join unittest thread: %d\n", status); 217 return false; 218 } 219 return success; 220} 221 222static int run_unittests_locked(int argc, const cmd_args* argv, uint32_t flags) { 223 DEBUG_ASSERT(is_mutex_held(&lock)); 224 if (argc != 2) { 225 usage(argv[0].str); 226 return 0; 227 } 228 229 const char* casename = argv[1].str; 230 231 if (!strcmp(casename, "?")) { 232 list_cases(); 233 return 0; 234 } 235 236 bool run_all = !strcmp(casename, "all"); 237 const unittest_testcase_registration_t* testcase; 238 size_t chosen = 0; 239 size_t passed = 0; 240 241 const size_t num_tests = 242 run_all ? __stop_unittest_testcases - __start_unittest_testcases : 1; 243 // Array of names with a NULL sentinel at the end. 244 const char** failed_names = static_cast<const char**>(calloc(num_tests + 1, sizeof(char*))); 245 const char** fn = failed_names; 246 247 for (testcase = __start_unittest_testcases; 248 testcase != __stop_unittest_testcases; 249 ++testcase) { 250 251 if (testcase->name) { 252 if (run_all || !strcmp(casename, testcase->name)) { 253 chosen++; 254 255 if (run_testcase_in_thread(testcase)) { 256 passed++; 257 } else { 258 *fn++ = testcase->name; 259 } 260 printf("\n"); 261 262 if (!run_all) 263 break; 264 } 265 } 266 } 267 268 int ret = 0; 269 if (!run_all && !chosen) { 270 ret = -1; 271 unittest_printf("Test case \"%s\" not found!\n", casename); 272 list_cases(); 273 } else { 274 unittest_printf("SUMMARY: Ran %d test case%s: %d failed\n", 275 chosen, chosen == 1 ? "" : "s", chosen - passed); 276 if (passed < chosen) { 277 ret = -1; 278 unittest_printf("\nThe following test cases failed:\n"); 279 for (fn = failed_names; *fn != NULL; fn++) { 280 unittest_printf("%s\n", *fn); 281 } 282 } 283 } 284 285 free(failed_names); 286 return ret; 287} 288 289static int run_unittests(int argc, const cmd_args* argv, uint32_t flags) { 290 mutex_acquire(&lock); 291 const int ret = run_unittests_locked(argc, argv, flags); 292 mutex_release(&lock); 293 return ret; 294} 295 296STATIC_COMMAND_START 297STATIC_COMMAND("ut", "Run unittests", run_unittests) 298STATIC_COMMAND_END(unittests); 299