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 "spr", // Sapphire Rapids 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 150func qemuOf(path string, args ...string) *exec.Cmd { 151 // The QEMU binary becomes the program to run, and the previous test program 152 // to run instead becomes an additional argument to the QEMU binary. 153 args = append([]string{path}, args...) 154 return exec.Command(*qemuBinary, args...) 155} 156 157var ( 158 errMoreMallocs = errors.New("child process did not exhaust all allocation calls") 159 errTestSkipped = errors.New("test was skipped") 160) 161 162func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) { 163 prog := path.Join(*buildDir, test.Cmd[0]) 164 args := append([]string{}, test.Cmd[1:]...) 165 if *simulateARMCPUs && test.cpu != "" { 166 args = append(args, "--cpu="+test.cpu) 167 } 168 if *useSDE { 169 // SDE is neither compatible with the unwind tester nor automatically 170 // detected. 171 args = append(args, "--no_unwind_tests") 172 } 173 174 var cmd *exec.Cmd 175 if *useValgrind { 176 cmd = valgrindOf(false, prog, args...) 177 } else if *useCallgrind { 178 cmd = callgrindOf(prog, args...) 179 } else if *useGDB { 180 cmd = gdbOf(prog, args...) 181 } else if *useSDE { 182 cmd = sdeOf(test.cpu, prog, args...) 183 } else if *qemuBinary != "" { 184 cmd = qemuOf(prog, args...) 185 } else { 186 cmd = exec.Command(prog, args...) 187 } 188 if test.Env != nil || test.numShards != 0 { 189 cmd.Env = make([]string, len(os.Environ())) 190 copy(cmd.Env, os.Environ()) 191 } 192 if test.Env != nil { 193 cmd.Env = append(cmd.Env, test.Env...) 194 } 195 if test.numShards != 0 { 196 cmd.Env = append(cmd.Env, fmt.Sprintf("GTEST_SHARD_INDEX=%d", test.shard)) 197 cmd.Env = append(cmd.Env, fmt.Sprintf("GTEST_TOTAL_SHARDS=%d", test.numShards)) 198 } 199 var outBuf bytes.Buffer 200 cmd.Stdout = &outBuf 201 cmd.Stderr = &outBuf 202 if mallocNumToFail >= 0 { 203 cmd.Env = os.Environ() 204 cmd.Env = append(cmd.Env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10)) 205 if *mallocTestDebug { 206 cmd.Env = append(cmd.Env, "MALLOC_ABORT_ON_FAIL=1") 207 } 208 cmd.Env = append(cmd.Env, "_MALLOC_CHECK=1") 209 } 210 211 if err := cmd.Start(); err != nil { 212 return false, err 213 } 214 if err := cmd.Wait(); err != nil { 215 if exitError, ok := err.(*exec.ExitError); ok { 216 switch exitError.Sys().(syscall.WaitStatus).ExitStatus() { 217 case 88: 218 return false, errMoreMallocs 219 case 89: 220 fmt.Print(string(outBuf.Bytes())) 221 return false, errTestSkipped 222 } 223 } 224 fmt.Print(string(outBuf.Bytes())) 225 return false, err 226 } 227 228 // Account for Windows line-endings. 229 stdout := bytes.Replace(outBuf.Bytes(), []byte("\r\n"), []byte("\n"), -1) 230 231 if bytes.HasSuffix(stdout, []byte("PASS\n")) && 232 (len(stdout) == 5 || stdout[len(stdout)-6] == '\n') { 233 return true, nil 234 } 235 236 // Also accept a googletest-style pass line. This is left here in 237 // transition until the tests are all converted and this script made 238 // unnecessary. 239 if bytes.Contains(stdout, []byte("\n[ PASSED ]")) { 240 return true, nil 241 } 242 243 fmt.Print(string(outBuf.Bytes())) 244 return false, nil 245} 246 247func runTest(test test) (bool, error) { 248 if *mallocTest < 0 { 249 return runTestOnce(test, -1) 250 } 251 252 for mallocNumToFail := int64(*mallocTest); ; mallocNumToFail++ { 253 if passed, err := runTestOnce(test, mallocNumToFail); err != errMoreMallocs { 254 if err != nil { 255 err = fmt.Errorf("at malloc %d: %s", mallocNumToFail, err) 256 } 257 return passed, err 258 } 259 } 260} 261 262// setWorkingDirectory walks up directories as needed until the current working 263// directory is the top of a BoringSSL checkout. 264func setWorkingDirectory() { 265 for i := 0; i < 64; i++ { 266 if _, err := os.Stat("BUILDING.md"); err == nil { 267 return 268 } 269 os.Chdir("..") 270 } 271 272 panic("Couldn't find BUILDING.md in a parent directory!") 273} 274 275func worker(tests <-chan test, results chan<- result, done *sync.WaitGroup) { 276 defer done.Done() 277 for test := range tests { 278 passed, err := runTest(test) 279 results <- result{test, passed, err} 280 } 281} 282 283func (t test) shortName() string { 284 return strings.Join(t.Cmd, " ") + t.shardMsg() + t.cpuMsg() + t.envMsg() 285} 286 287func SpaceIf(returnSpace bool) string { 288 if !returnSpace { 289 return "" 290 } 291 return " " 292} 293 294func (t test) longName() string { 295 return strings.Join(t.Env, " ") + SpaceIf(len(t.Env) != 0) + strings.Join(t.Cmd, " ") + t.shardMsg() + t.cpuMsg() 296} 297 298func (t test) shardMsg() string { 299 if t.numShards == 0 { 300 return "" 301 } 302 303 return fmt.Sprintf(" [shard %d/%d]", t.shard+1, t.numShards) 304} 305 306func (t test) cpuMsg() string { 307 if len(t.cpu) == 0 { 308 return "" 309 } 310 311 return fmt.Sprintf(" (for CPU %q)", t.cpu) 312} 313 314func (t test) envMsg() string { 315 if len(t.Env) == 0 { 316 return "" 317 } 318 319 return " (custom environment)" 320} 321 322func (t test) getGTestShards() ([]test, error) { 323 if *numWorkers == 1 || !t.Shard { 324 return []test{t}, nil 325 } 326 327 shards := make([]test, *numWorkers) 328 for i := range shards { 329 shards[i] = t 330 shards[i].shard = i 331 shards[i].numShards = *numWorkers 332 } 333 334 return shards, nil 335} 336 337func main() { 338 flag.Parse() 339 setWorkingDirectory() 340 341 testCases, err := testconfig.ParseTestConfig("util/all_tests.json") 342 if err != nil { 343 fmt.Printf("Failed to parse input: %s\n", err) 344 os.Exit(1) 345 } 346 347 var wg sync.WaitGroup 348 tests := make(chan test, *numWorkers) 349 results := make(chan result, *numWorkers) 350 351 for i := 0; i < *numWorkers; i++ { 352 wg.Add(1) 353 go worker(tests, results, &wg) 354 } 355 356 go func() { 357 for _, baseTest := range testCases { 358 test := test{Test: baseTest} 359 if *useSDE { 360 if test.SkipSDE { 361 continue 362 } 363 // SDE generates plenty of tasks and gets slower 364 // with additional sharding. 365 for _, cpu := range sdeCPUs { 366 testForCPU := test 367 testForCPU.cpu = cpu 368 tests <- testForCPU 369 } 370 } else if *simulateARMCPUs { 371 // This mode is run instead of the default path, 372 // so also include the native flow. 373 tests <- test 374 for _, cpu := range armCPUs { 375 testForCPU := test 376 testForCPU.cpu = cpu 377 tests <- testForCPU 378 } 379 } else { 380 shards, err := test.getGTestShards() 381 if err != nil { 382 fmt.Printf("Error listing tests: %s\n", err) 383 os.Exit(1) 384 } 385 for _, shard := range shards { 386 tests <- shard 387 } 388 } 389 } 390 close(tests) 391 392 wg.Wait() 393 close(results) 394 }() 395 396 testOutput := testresult.NewResults() 397 var failed, skipped []test 398 var total int 399 for testResult := range results { 400 test := testResult.Test 401 args := test.Cmd 402 403 total++ 404 if testResult.Error == errTestSkipped { 405 fmt.Printf("%s\n", test.longName()) 406 fmt.Printf("%s was skipped\n", args[0]) 407 skipped = append(skipped, test) 408 testOutput.AddSkip(test.longName()) 409 } else if testResult.Error != nil { 410 fmt.Printf("%s\n", test.longName()) 411 fmt.Printf("%s failed to complete: %s\n", args[0], testResult.Error) 412 failed = append(failed, test) 413 testOutput.AddResult(test.longName(), "CRASH", testResult.Error) 414 } else if !testResult.Passed { 415 fmt.Printf("%s\n", test.longName()) 416 fmt.Printf("%s failed to print PASS on the last line.\n", args[0]) 417 failed = append(failed, test) 418 testOutput.AddResult(test.longName(), "FAIL", nil) 419 } else { 420 fmt.Printf("%s\n", test.shortName()) 421 testOutput.AddResult(test.longName(), "PASS", nil) 422 } 423 } 424 425 if *jsonOutput != "" { 426 if err := testOutput.WriteToFile(*jsonOutput); err != nil { 427 fmt.Fprintf(os.Stderr, "Error: %s\n", err) 428 } 429 } 430 431 if len(skipped) > 0 { 432 fmt.Printf("\n%d of %d tests were skipped:\n", len(skipped), total) 433 for _, test := range skipped { 434 fmt.Printf("\t%s\n", test.shortName()) 435 } 436 } 437 438 if len(failed) > 0 { 439 fmt.Printf("\n%d of %d tests failed:\n", len(failed), total) 440 for _, test := range failed { 441 fmt.Printf("\t%s\n", test.shortName()) 442 } 443 os.Exit(1) 444 } 445 446 fmt.Printf("All unit tests passed!\n") 447} 448