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