1// Copyright (c) 2015, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15//go:build ignore 16 17package main 18 19import ( 20 "bytes" 21 "errors" 22 "flag" 23 "fmt" 24 "os" 25 "os/exec" 26 "path" 27 "runtime" 28 "strconv" 29 "strings" 30 "sync" 31 "syscall" 32 33 "boringssl.googlesource.com/boringssl/util/testconfig" 34 "boringssl.googlesource.com/boringssl/util/testresult" 35) 36 37// TODO(davidben): Link tests with the malloc shim and port -malloc-test to this runner. 38 39var ( 40 useValgrind = flag.Bool("valgrind", false, "If true, run code under valgrind") 41 useCallgrind = flag.Bool("callgrind", false, "If true, run code under valgrind to generate callgrind traces.") 42 useGDB = flag.Bool("gdb", false, "If true, run BoringSSL code under gdb") 43 useSDE = flag.Bool("sde", false, "If true, run BoringSSL code under Intel's SDE for each supported chip") 44 sdePath = flag.String("sde-path", "sde", "The path to find the sde binary.") 45 buildDir = flag.String("build-dir", "build", "The build directory to run the tests from.") 46 numWorkers = flag.Int("num-workers", runtime.NumCPU(), "Runs the given number of workers when testing.") 47 jsonOutput = flag.String("json-output", "", "The file to output JSON results to.") 48 mallocTest = flag.Int64("malloc-test", -1, "If non-negative, run each test with each malloc in turn failing from the given number onwards.") 49 mallocTestDebug = flag.Bool("malloc-test-debug", false, "If true, ask each test to abort rather than fail a malloc. This can be used with a specific value for --malloc-test to identity the malloc failing that is causing problems.") 50 simulateARMCPUs = flag.Bool("simulate-arm-cpus", simulateARMCPUsDefault(), "If true, runs tests simulating different ARM CPUs.") 51 qemuBinary = flag.String("qemu", "", "Optional, absolute path to a binary location for QEMU runtime.") 52) 53 54func simulateARMCPUsDefault() bool { 55 return (runtime.GOOS == "linux" || runtime.GOOS == "android") && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") 56} 57 58type test struct { 59 testconfig.Test 60 61 shard, numShards int 62 // cpu, if not empty, contains a code to simulate. For SDE, run `sde64 63 // -help` to get a list of these codes. For ARM, see gtest_main.cc for 64 // the supported values. 65 cpu string 66} 67 68type result struct { 69 Test test 70 Passed bool 71 Error error 72} 73 74// sdeCPUs contains a list of CPU code that we run all tests under when *useSDE 75// is true. 76var sdeCPUs = []string{ 77 "p4p", // Pentium4 Prescott 78 "mrm", // Merom 79 "pnr", // Penryn 80 "nhm", // Nehalem 81 "wsm", // Westmere 82 "snb", // Sandy Bridge 83 "ivb", // Ivy Bridge 84 "hsw", // Haswell 85 "bdw", // Broadwell 86 "slt", // Saltwell 87 "slm", // Silvermont 88 "glm", // Goldmont 89 "glp", // Goldmont Plus 90 "tnt", // Tremont 91 "skl", // Skylake 92 "cnl", // Cannon Lake 93 "icl", // Ice Lake 94 "skx", // Skylake server 95 "clx", // Cascade Lake 96 "cpx", // Cooper Lake 97 "icx", // Ice Lake server 98 "tgl", // Tiger Lake 99} 100 101var armCPUs = []string{ 102 "none", // No support for any ARM extensions. 103 "neon", // Support for NEON. 104 "crypto", // Support for NEON and crypto extensions. 105} 106 107func valgrindOf(dbAttach bool, path string, args ...string) *exec.Cmd { 108 valgrindArgs := []string{"--error-exitcode=99", "--track-origins=yes", "--leak-check=full", "--quiet"} 109 if dbAttach { 110 valgrindArgs = append(valgrindArgs, "--db-attach=yes", "--db-command=xterm -e gdb -nw %f %p") 111 } 112 valgrindArgs = append(valgrindArgs, path) 113 valgrindArgs = append(valgrindArgs, args...) 114 115 return exec.Command("valgrind", valgrindArgs...) 116} 117 118func callgrindOf(path string, args ...string) *exec.Cmd { 119 valgrindArgs := []string{"-q", "--tool=callgrind", "--dump-instr=yes", "--collect-jumps=yes", "--callgrind-out-file=" + *buildDir + "/callgrind/callgrind.out.%p"} 120 valgrindArgs = append(valgrindArgs, path) 121 valgrindArgs = append(valgrindArgs, args...) 122 123 return exec.Command("valgrind", valgrindArgs...) 124} 125 126func gdbOf(path string, args ...string) *exec.Cmd { 127 xtermArgs := []string{"-e", "gdb", "--args"} 128 xtermArgs = append(xtermArgs, path) 129 xtermArgs = append(xtermArgs, args...) 130 131 return exec.Command("xterm", xtermArgs...) 132} 133 134func sdeOf(cpu, path string, args ...string) *exec.Cmd { 135 sdeArgs := []string{"-" + cpu} 136 // The kernel's vdso code for gettimeofday sometimes uses the RDTSCP 137 // instruction. Although SDE has a -chip_check_vsyscall flag that 138 // excludes such code by default, it does not seem to work. Instead, 139 // pass the -chip_check_exe_only flag which retains test coverage when 140 // statically linked and excludes the vdso. 141 if cpu == "p4p" || cpu == "pnr" || cpu == "mrm" || cpu == "slt" { 142 sdeArgs = append(sdeArgs, "-chip_check_exe_only") 143 } 144 sdeArgs = append(sdeArgs, "--", path) 145 sdeArgs = append(sdeArgs, args...) 146 return exec.Command(*sdePath, sdeArgs...) 147} 148 149func qemuOf(path string, args ...string) *exec.Cmd { 150 // The QEMU binary becomes the program to run, and the previous test program 151 // to run instead becomes an additional argument to the QEMU binary. 152 args = append([]string{path}, args...) 153 return exec.Command(*qemuBinary, args...) 154} 155 156var ( 157 errMoreMallocs = errors.New("child process did not exhaust all allocation calls") 158 errTestSkipped = errors.New("test was skipped") 159) 160 161func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) { 162 prog := path.Join(*buildDir, test.Cmd[0]) 163 args := append([]string{}, test.Cmd[1:]...) 164 if *simulateARMCPUs && test.cpu != "" { 165 args = append(args, "--cpu="+test.cpu) 166 } 167 if *useSDE { 168 // SDE is neither compatible with the unwind tester nor automatically 169 // detected. 170 args = append(args, "--no_unwind_tests") 171 } 172 173 var cmd *exec.Cmd 174 if *useValgrind { 175 cmd = valgrindOf(false, prog, args...) 176 } else if *useCallgrind { 177 cmd = callgrindOf(prog, args...) 178 } else if *useGDB { 179 cmd = gdbOf(prog, args...) 180 } else if *useSDE { 181 cmd = sdeOf(test.cpu, prog, args...) 182 } else if *qemuBinary != "" { 183 cmd = qemuOf(prog, args...) 184 } else { 185 cmd = exec.Command(prog, args...) 186 } 187 if test.Env != nil || test.numShards != 0 { 188 cmd.Env = make([]string, len(os.Environ())) 189 copy(cmd.Env, os.Environ()) 190 } 191 if test.Env != nil { 192 cmd.Env = append(cmd.Env, test.Env...) 193 } 194 if test.numShards != 0 { 195 cmd.Env = append(cmd.Env, fmt.Sprintf("GTEST_SHARD_INDEX=%d", test.shard)) 196 cmd.Env = append(cmd.Env, fmt.Sprintf("GTEST_TOTAL_SHARDS=%d", test.numShards)) 197 } 198 var outBuf bytes.Buffer 199 cmd.Stdout = &outBuf 200 cmd.Stderr = &outBuf 201 if mallocNumToFail >= 0 { 202 cmd.Env = os.Environ() 203 cmd.Env = append(cmd.Env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10)) 204 if *mallocTestDebug { 205 cmd.Env = append(cmd.Env, "MALLOC_ABORT_ON_FAIL=1") 206 } 207 cmd.Env = append(cmd.Env, "_MALLOC_CHECK=1") 208 } 209 210 if err := cmd.Start(); err != nil { 211 return false, err 212 } 213 if err := cmd.Wait(); err != nil { 214 if exitError, ok := err.(*exec.ExitError); ok { 215 switch exitError.Sys().(syscall.WaitStatus).ExitStatus() { 216 case 88: 217 return false, errMoreMallocs 218 case 89: 219 fmt.Print(string(outBuf.Bytes())) 220 return false, errTestSkipped 221 } 222 } 223 fmt.Print(string(outBuf.Bytes())) 224 return false, err 225 } 226 227 // Account for Windows line-endings. 228 stdout := bytes.Replace(outBuf.Bytes(), []byte("\r\n"), []byte("\n"), -1) 229 230 if bytes.HasSuffix(stdout, []byte("PASS\n")) && 231 (len(stdout) == 5 || stdout[len(stdout)-6] == '\n') { 232 return true, nil 233 } 234 235 // Also accept a googletest-style pass line. This is left here in 236 // transition until the tests are all converted and this script made 237 // unnecessary. 238 if bytes.Contains(stdout, []byte("\n[ PASSED ]")) { 239 return true, nil 240 } 241 242 fmt.Print(string(outBuf.Bytes())) 243 return false, nil 244} 245 246func runTest(test test) (bool, error) { 247 if *mallocTest < 0 { 248 return runTestOnce(test, -1) 249 } 250 251 for mallocNumToFail := int64(*mallocTest); ; mallocNumToFail++ { 252 if passed, err := runTestOnce(test, mallocNumToFail); err != errMoreMallocs { 253 if err != nil { 254 err = fmt.Errorf("at malloc %d: %s", mallocNumToFail, err) 255 } 256 return passed, err 257 } 258 } 259} 260 261// setWorkingDirectory walks up directories as needed until the current working 262// directory is the top of a BoringSSL checkout. 263func setWorkingDirectory() { 264 for i := 0; i < 64; i++ { 265 if _, err := os.Stat("BUILDING.md"); err == nil { 266 return 267 } 268 os.Chdir("..") 269 } 270 271 panic("Couldn't find BUILDING.md in a parent directory!") 272} 273 274func worker(tests <-chan test, results chan<- result, done *sync.WaitGroup) { 275 defer done.Done() 276 for test := range tests { 277 passed, err := runTest(test) 278 results <- result{test, passed, err} 279 } 280} 281 282func (t test) shortName() string { 283 return strings.Join(t.Cmd, " ") + t.shardMsg() + t.cpuMsg() + t.envMsg() 284} 285 286func SpaceIf(returnSpace bool) string { 287 if !returnSpace { 288 return "" 289 } 290 return " " 291} 292 293func (t test) longName() string { 294 return strings.Join(t.Env, " ") + SpaceIf(len(t.Env) != 0) + strings.Join(t.Cmd, " ") + t.shardMsg() + t.cpuMsg() 295} 296 297func (t test) shardMsg() string { 298 if t.numShards == 0 { 299 return "" 300 } 301 302 return fmt.Sprintf(" [shard %d/%d]", t.shard+1, t.numShards) 303} 304 305func (t test) cpuMsg() string { 306 if len(t.cpu) == 0 { 307 return "" 308 } 309 310 return fmt.Sprintf(" (for CPU %q)", t.cpu) 311} 312 313func (t test) envMsg() string { 314 if len(t.Env) == 0 { 315 return "" 316 } 317 318 return " (custom environment)" 319} 320 321func (t test) getGTestShards() ([]test, error) { 322 if *numWorkers == 1 || !t.Shard { 323 return []test{t}, nil 324 } 325 326 shards := make([]test, *numWorkers) 327 for i := range shards { 328 shards[i] = t 329 shards[i].shard = i 330 shards[i].numShards = *numWorkers 331 } 332 333 return shards, nil 334} 335 336func main() { 337 flag.Parse() 338 setWorkingDirectory() 339 340 testCases, err := testconfig.ParseTestConfig("util/all_tests.json") 341 if err != nil { 342 fmt.Printf("Failed to parse input: %s\n", err) 343 os.Exit(1) 344 } 345 346 var wg sync.WaitGroup 347 tests := make(chan test, *numWorkers) 348 results := make(chan result, *numWorkers) 349 350 for i := 0; i < *numWorkers; i++ { 351 wg.Add(1) 352 go worker(tests, results, &wg) 353 } 354 355 go func() { 356 for _, baseTest := range testCases { 357 test := test{Test: baseTest} 358 if *useSDE { 359 if test.SkipSDE { 360 continue 361 } 362 // SDE generates plenty of tasks and gets slower 363 // with additional sharding. 364 for _, cpu := range sdeCPUs { 365 testForCPU := test 366 testForCPU.cpu = cpu 367 tests <- testForCPU 368 } 369 } else if *simulateARMCPUs { 370 // This mode is run instead of the default path, 371 // so also include the native flow. 372 tests <- test 373 for _, cpu := range armCPUs { 374 testForCPU := test 375 testForCPU.cpu = cpu 376 tests <- testForCPU 377 } 378 } else { 379 shards, err := test.getGTestShards() 380 if err != nil { 381 fmt.Printf("Error listing tests: %s\n", err) 382 os.Exit(1) 383 } 384 for _, shard := range shards { 385 tests <- shard 386 } 387 } 388 } 389 close(tests) 390 391 wg.Wait() 392 close(results) 393 }() 394 395 testOutput := testresult.NewResults() 396 var failed, skipped []test 397 for testResult := range results { 398 test := testResult.Test 399 args := test.Cmd 400 401 if testResult.Error == errTestSkipped { 402 fmt.Printf("%s\n", test.longName()) 403 fmt.Printf("%s was skipped\n", args[0]) 404 skipped = append(skipped, test) 405 testOutput.AddSkip(test.longName()) 406 } else if testResult.Error != nil { 407 fmt.Printf("%s\n", test.longName()) 408 fmt.Printf("%s failed to complete: %s\n", args[0], testResult.Error) 409 failed = append(failed, test) 410 testOutput.AddResult(test.longName(), "CRASH", testResult.Error) 411 } else if !testResult.Passed { 412 fmt.Printf("%s\n", test.longName()) 413 fmt.Printf("%s failed to print PASS on the last line.\n", args[0]) 414 failed = append(failed, test) 415 testOutput.AddResult(test.longName(), "FAIL", nil) 416 } else { 417 fmt.Printf("%s\n", test.shortName()) 418 testOutput.AddResult(test.longName(), "PASS", nil) 419 } 420 } 421 422 if *jsonOutput != "" { 423 if err := testOutput.WriteToFile(*jsonOutput); err != nil { 424 fmt.Fprintf(os.Stderr, "Error: %s\n", err) 425 } 426 } 427 428 if len(skipped) > 0 { 429 fmt.Printf("\n%d of %d tests were skipped:\n", len(skipped), len(testCases)) 430 for _, test := range skipped { 431 fmt.Printf("\t%s\n", test.shortName()) 432 } 433 } 434 435 if len(failed) > 0 { 436 fmt.Printf("\n%d of %d tests failed:\n", len(failed), len(testCases)) 437 for _, test := range failed { 438 fmt.Printf("\t%s\n", test.shortName()) 439 } 440 os.Exit(1) 441 } 442 443 fmt.Printf("All unit tests passed!\n") 444} 445