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