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 15package main 16 17import ( 18 "bufio" 19 "bytes" 20 "errors" 21 "flag" 22 "fmt" 23 "math/rand" 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) 52 53func simulateARMCPUsDefault() bool { 54 return (runtime.GOOS == "linux" || runtime.GOOS == "android") && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") 55} 56 57type test struct { 58 testconfig.Test 59 60 shard, numShards int 61 // cpu, if not empty, contains a code to simulate. For SDE, run `sde64 62 // -help` to get a list of these codes. For ARM, see gtest_main.cc for 63 // the supported values. 64 cpu string 65} 66 67type result struct { 68 Test test 69 Passed bool 70 Error error 71} 72 73// sdeCPUs contains a list of CPU code that we run all tests under when *useSDE 74// is true. 75var sdeCPUs = []string{ 76 "p4p", // Pentium4 Prescott 77 "mrm", // Merom 78 "pnr", // Penryn 79 "nhm", // Nehalem 80 "wsm", // Westmere 81 "snb", // Sandy Bridge 82 "ivb", // Ivy Bridge 83 "hsw", // Haswell 84 "bdw", // Broadwell 85 "slt", // Saltwell 86 "slm", // Silvermont 87 "glm", // Goldmont 88 "glp", // Goldmont Plus 89 "tnt", // Tremont 90 "skl", // Skylake 91 "cnl", // Cannon Lake 92 "icl", // Ice Lake 93 "skx", // Skylake server 94 "clx", // Cascade Lake 95 "cpx", // Cooper Lake 96 "icx", // Ice Lake server 97 "knl", // Knights landing 98 "knm", // Knights mill 99 "tgl", // Tiger Lake 100} 101 102var armCPUs = []string{ 103 "none", // No support for any ARM extensions. 104 "neon", // Support for NEON. 105 "crypto", // Support for NEON and crypto extensions. 106} 107 108func valgrindOf(dbAttach bool, path string, args ...string) *exec.Cmd { 109 valgrindArgs := []string{"--error-exitcode=99", "--track-origins=yes", "--leak-check=full", "--quiet"} 110 if dbAttach { 111 valgrindArgs = append(valgrindArgs, "--db-attach=yes", "--db-command=xterm -e gdb -nw %f %p") 112 } 113 valgrindArgs = append(valgrindArgs, path) 114 valgrindArgs = append(valgrindArgs, args...) 115 116 return exec.Command("valgrind", valgrindArgs...) 117} 118 119func callgrindOf(path string, args ...string) *exec.Cmd { 120 valgrindArgs := []string{"-q", "--tool=callgrind", "--dump-instr=yes", "--collect-jumps=yes", "--callgrind-out-file=" + *buildDir + "/callgrind/callgrind.out.%p"} 121 valgrindArgs = append(valgrindArgs, path) 122 valgrindArgs = append(valgrindArgs, args...) 123 124 return exec.Command("valgrind", valgrindArgs...) 125} 126 127func gdbOf(path string, args ...string) *exec.Cmd { 128 xtermArgs := []string{"-e", "gdb", "--args"} 129 xtermArgs = append(xtermArgs, path) 130 xtermArgs = append(xtermArgs, args...) 131 132 return exec.Command("xterm", xtermArgs...) 133} 134 135func sdeOf(cpu, path string, args ...string) *exec.Cmd { 136 sdeArgs := []string{"-" + cpu} 137 // The kernel's vdso code for gettimeofday sometimes uses the RDTSCP 138 // instruction. Although SDE has a -chip_check_vsyscall flag that 139 // excludes such code by default, it does not seem to work. Instead, 140 // pass the -chip_check_exe_only flag which retains test coverage when 141 // statically linked and excludes the vdso. 142 if cpu == "p4p" || cpu == "pnr" || cpu == "mrm" || cpu == "slt" { 143 sdeArgs = append(sdeArgs, "-chip_check_exe_only") 144 } 145 sdeArgs = append(sdeArgs, "--", path) 146 sdeArgs = append(sdeArgs, args...) 147 return exec.Command(*sdePath, sdeArgs...) 148} 149 150var ( 151 errMoreMallocs = errors.New("child process did not exhaust all allocation calls") 152 errTestSkipped = errors.New("test was skipped") 153) 154 155func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) { 156 prog := path.Join(*buildDir, test.Cmd[0]) 157 args := append([]string{}, test.Cmd[1:]...) 158 if *simulateARMCPUs && test.cpu != "" { 159 args = append(args, "--cpu="+test.cpu) 160 } 161 if *useSDE { 162 // SDE is neither compatible with the unwind tester nor automatically 163 // detected. 164 args = append(args, "--no_unwind_tests") 165 } 166 var cmd *exec.Cmd 167 if *useValgrind { 168 cmd = valgrindOf(false, prog, args...) 169 } else if *useCallgrind { 170 cmd = callgrindOf(prog, args...) 171 } else if *useGDB { 172 cmd = gdbOf(prog, args...) 173 } else if *useSDE { 174 cmd = sdeOf(test.cpu, prog, args...) 175 } else { 176 cmd = exec.Command(prog, args...) 177 } 178 if test.Env != nil { 179 cmd.Env = make([]string, len(os.Environ())) 180 copy(cmd.Env, os.Environ()) 181 cmd.Env = append(cmd.Env, test.Env...) 182 } 183 var outBuf bytes.Buffer 184 cmd.Stdout = &outBuf 185 cmd.Stderr = &outBuf 186 if mallocNumToFail >= 0 { 187 cmd.Env = os.Environ() 188 cmd.Env = append(cmd.Env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10)) 189 if *mallocTestDebug { 190 cmd.Env = append(cmd.Env, "MALLOC_ABORT_ON_FAIL=1") 191 } 192 cmd.Env = append(cmd.Env, "_MALLOC_CHECK=1") 193 } 194 195 if err := cmd.Start(); err != nil { 196 return false, err 197 } 198 if err := cmd.Wait(); err != nil { 199 if exitError, ok := err.(*exec.ExitError); ok { 200 switch exitError.Sys().(syscall.WaitStatus).ExitStatus() { 201 case 88: 202 return false, errMoreMallocs 203 case 89: 204 fmt.Print(string(outBuf.Bytes())) 205 return false, errTestSkipped 206 } 207 } 208 fmt.Print(string(outBuf.Bytes())) 209 return false, err 210 } 211 212 // Account for Windows line-endings. 213 stdout := bytes.Replace(outBuf.Bytes(), []byte("\r\n"), []byte("\n"), -1) 214 215 if bytes.HasSuffix(stdout, []byte("PASS\n")) && 216 (len(stdout) == 5 || stdout[len(stdout)-6] == '\n') { 217 return true, nil 218 } 219 220 // Also accept a googletest-style pass line. This is left here in 221 // transition until the tests are all converted and this script made 222 // unnecessary. 223 if bytes.Contains(stdout, []byte("\n[ PASSED ]")) { 224 return true, nil 225 } 226 227 fmt.Print(string(outBuf.Bytes())) 228 return false, nil 229} 230 231func runTest(test test) (bool, error) { 232 if *mallocTest < 0 { 233 return runTestOnce(test, -1) 234 } 235 236 for mallocNumToFail := int64(*mallocTest); ; mallocNumToFail++ { 237 if passed, err := runTestOnce(test, mallocNumToFail); err != errMoreMallocs { 238 if err != nil { 239 err = fmt.Errorf("at malloc %d: %s", mallocNumToFail, err) 240 } 241 return passed, err 242 } 243 } 244} 245 246// setWorkingDirectory walks up directories as needed until the current working 247// directory is the top of a BoringSSL checkout. 248func setWorkingDirectory() { 249 for i := 0; i < 64; i++ { 250 if _, err := os.Stat("BUILDING.md"); err == nil { 251 return 252 } 253 os.Chdir("..") 254 } 255 256 panic("Couldn't find BUILDING.md in a parent directory!") 257} 258 259func worker(tests <-chan test, results chan<- result, done *sync.WaitGroup) { 260 defer done.Done() 261 for test := range tests { 262 passed, err := runTest(test) 263 results <- result{test, passed, err} 264 } 265} 266 267func (t test) shortName() string { 268 return t.Cmd[0] + t.shardMsg() + t.cpuMsg() + t.envMsg() 269} 270 271func SpaceIf(returnSpace bool) string { 272 if !returnSpace { 273 return "" 274 } 275 return " " 276} 277 278func (t test) longName() string { 279 return strings.Join(t.Env, " ") + SpaceIf(len(t.Env) != 0) + strings.Join(t.Cmd, " ") + t.cpuMsg() 280} 281 282func (t test) shardMsg() string { 283 if t.numShards == 0 { 284 return "" 285 } 286 287 return fmt.Sprintf(" [shard %d/%d]", t.shard+1, t.numShards) 288} 289 290func (t test) cpuMsg() string { 291 if len(t.cpu) == 0 { 292 return "" 293 } 294 295 return fmt.Sprintf(" (for CPU %q)", t.cpu) 296} 297 298func (t test) envMsg() string { 299 if len(t.Env) == 0 { 300 return "" 301 } 302 303 return " (custom environment)" 304} 305 306func (t test) getGTestShards() ([]test, error) { 307 if *numWorkers == 1 || len(t.Cmd) != 1 { 308 return []test{t}, nil 309 } 310 311 // Only shard the three GTest-based tests. 312 if t.Cmd[0] != "crypto/crypto_test" && t.Cmd[0] != "ssl/ssl_test" && t.Cmd[0] != "decrepit/decrepit_test" { 313 return []test{t}, nil 314 } 315 316 prog := path.Join(*buildDir, t.Cmd[0]) 317 cmd := exec.Command(prog, "--gtest_list_tests") 318 var stdout bytes.Buffer 319 cmd.Stdout = &stdout 320 if err := cmd.Start(); err != nil { 321 return nil, err 322 } 323 if err := cmd.Wait(); err != nil { 324 return nil, err 325 } 326 327 var group string 328 var tests []string 329 scanner := bufio.NewScanner(&stdout) 330 for scanner.Scan() { 331 line := scanner.Text() 332 333 // Remove the parameter comment and trailing space. 334 if idx := strings.Index(line, "#"); idx >= 0 { 335 line = line[:idx] 336 } 337 line = strings.TrimSpace(line) 338 if len(line) == 0 { 339 continue 340 } 341 342 if line[len(line)-1] == '.' { 343 group = line 344 continue 345 } 346 347 if len(group) == 0 { 348 return nil, fmt.Errorf("found test case %q without group", line) 349 } 350 tests = append(tests, group+line) 351 } 352 353 const testsPerShard = 20 354 if len(tests) <= testsPerShard { 355 return []test{t}, nil 356 } 357 358 // Slow tests which process large test vector files tend to be grouped 359 // together, so shuffle the order. 360 shuffled := make([]string, len(tests)) 361 perm := rand.Perm(len(tests)) 362 for i, j := range perm { 363 shuffled[i] = tests[j] 364 } 365 366 var shards []test 367 for i := 0; i < len(shuffled); i += testsPerShard { 368 n := len(shuffled) - i 369 if n > testsPerShard { 370 n = testsPerShard 371 } 372 shard := t 373 shard.Cmd = []string{shard.Cmd[0], "--gtest_filter=" + strings.Join(shuffled[i:i+n], ":")} 374 shard.shard = len(shards) 375 shards = append(shards, shard) 376 } 377 378 for i := range shards { 379 shards[i].numShards = len(shards) 380 } 381 382 return shards, nil 383} 384 385func main() { 386 flag.Parse() 387 setWorkingDirectory() 388 389 testCases, err := testconfig.ParseTestConfig("util/all_tests.json") 390 if err != nil { 391 fmt.Printf("Failed to parse input: %s\n", err) 392 os.Exit(1) 393 } 394 395 var wg sync.WaitGroup 396 tests := make(chan test, *numWorkers) 397 results := make(chan result, *numWorkers) 398 399 for i := 0; i < *numWorkers; i++ { 400 wg.Add(1) 401 go worker(tests, results, &wg) 402 } 403 404 go func() { 405 for _, baseTest := range testCases { 406 test := test{Test: baseTest} 407 if *useSDE { 408 if test.SkipSDE { 409 continue 410 } 411 // SDE generates plenty of tasks and gets slower 412 // with additional sharding. 413 for _, cpu := range sdeCPUs { 414 testForCPU := test 415 testForCPU.cpu = cpu 416 tests <- testForCPU 417 } 418 } else if *simulateARMCPUs { 419 // This mode is run instead of the default path, 420 // so also include the native flow. 421 tests <- test 422 for _, cpu := range armCPUs { 423 testForCPU := test 424 testForCPU.cpu = cpu 425 tests <- testForCPU 426 } 427 } else { 428 shards, err := test.getGTestShards() 429 if err != nil { 430 fmt.Printf("Error listing tests: %s\n", err) 431 os.Exit(1) 432 } 433 for _, shard := range shards { 434 tests <- shard 435 } 436 } 437 } 438 close(tests) 439 440 wg.Wait() 441 close(results) 442 }() 443 444 testOutput := testresult.NewResults() 445 var failed, skipped []test 446 for testResult := range results { 447 test := testResult.Test 448 args := test.Cmd 449 450 if testResult.Error == errTestSkipped { 451 fmt.Printf("%s\n", test.longName()) 452 fmt.Printf("%s was skipped\n", args[0]) 453 skipped = append(skipped, test) 454 testOutput.AddSkip(test.longName()) 455 } else if testResult.Error != nil { 456 fmt.Printf("%s\n", test.longName()) 457 fmt.Printf("%s failed to complete: %s\n", args[0], testResult.Error) 458 failed = append(failed, test) 459 testOutput.AddResult(test.longName(), "CRASH") 460 } else if !testResult.Passed { 461 fmt.Printf("%s\n", test.longName()) 462 fmt.Printf("%s failed to print PASS on the last line.\n", args[0]) 463 failed = append(failed, test) 464 testOutput.AddResult(test.longName(), "FAIL") 465 } else { 466 fmt.Printf("%s\n", test.shortName()) 467 testOutput.AddResult(test.longName(), "PASS") 468 } 469 } 470 471 if *jsonOutput != "" { 472 if err := testOutput.WriteToFile(*jsonOutput); err != nil { 473 fmt.Fprintf(os.Stderr, "Error: %s\n", err) 474 } 475 } 476 477 if len(skipped) > 0 { 478 fmt.Printf("\n%d of %d tests were skipped:\n", len(skipped), len(testCases)) 479 for _, test := range skipped { 480 fmt.Printf("\t%s%s\n", strings.Join(test.Cmd, " "), test.cpuMsg()) 481 } 482 } 483 484 if len(failed) > 0 { 485 fmt.Printf("\n%d of %d tests failed:\n", len(failed), len(testCases)) 486 for _, test := range failed { 487 fmt.Printf("\t%s%s\n", strings.Join(test.Cmd, " "), test.cpuMsg()) 488 } 489 os.Exit(1) 490 } 491 492 fmt.Printf("\nAll tests passed!\n") 493} 494