|  | // Copyright (c) 2015, Google Inc. | 
|  | // | 
|  | // Permission to use, copy, modify, and/or distribute this software for any | 
|  | // purpose with or without fee is hereby granted, provided that the above | 
|  | // copyright notice and this permission notice appear in all copies. | 
|  | // | 
|  | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
|  | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
|  | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | 
|  | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
|  | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | 
|  | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | 
|  | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
|  |  | 
|  | //go:build ignore | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "errors" | 
|  | "flag" | 
|  | "fmt" | 
|  | "os" | 
|  | "os/exec" | 
|  | "path" | 
|  | "runtime" | 
|  | "strconv" | 
|  | "strings" | 
|  | "sync" | 
|  | "syscall" | 
|  |  | 
|  | "boringssl.googlesource.com/boringssl/util/testconfig" | 
|  | "boringssl.googlesource.com/boringssl/util/testresult" | 
|  | ) | 
|  |  | 
|  | // TODO(davidben): Link tests with the malloc shim and port -malloc-test to this runner. | 
|  |  | 
|  | var ( | 
|  | useValgrind     = flag.Bool("valgrind", false, "If true, run code under valgrind") | 
|  | useCallgrind    = flag.Bool("callgrind", false, "If true, run code under valgrind to generate callgrind traces.") | 
|  | useGDB          = flag.Bool("gdb", false, "If true, run BoringSSL code under gdb") | 
|  | useSDE          = flag.Bool("sde", false, "If true, run BoringSSL code under Intel's SDE for each supported chip") | 
|  | sdePath         = flag.String("sde-path", "sde", "The path to find the sde binary.") | 
|  | buildDir        = flag.String("build-dir", "build", "The build directory to run the tests from.") | 
|  | numWorkers      = flag.Int("num-workers", runtime.NumCPU(), "Runs the given number of workers when testing.") | 
|  | jsonOutput      = flag.String("json-output", "", "The file to output JSON results to.") | 
|  | mallocTest      = flag.Int64("malloc-test", -1, "If non-negative, run each test with each malloc in turn failing from the given number onwards.") | 
|  | 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.") | 
|  | simulateARMCPUs = flag.Bool("simulate-arm-cpus", simulateARMCPUsDefault(), "If true, runs tests simulating different ARM CPUs.") | 
|  | ) | 
|  |  | 
|  | func simulateARMCPUsDefault() bool { | 
|  | return (runtime.GOOS == "linux" || runtime.GOOS == "android") && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") | 
|  | } | 
|  |  | 
|  | type test struct { | 
|  | testconfig.Test | 
|  |  | 
|  | shard, numShards int | 
|  | // cpu, if not empty, contains a code to simulate. For SDE, run `sde64 | 
|  | // -help` to get a list of these codes. For ARM, see gtest_main.cc for | 
|  | // the supported values. | 
|  | cpu string | 
|  | } | 
|  |  | 
|  | type result struct { | 
|  | Test   test | 
|  | Passed bool | 
|  | Error  error | 
|  | } | 
|  |  | 
|  | // sdeCPUs contains a list of CPU code that we run all tests under when *useSDE | 
|  | // is true. | 
|  | var sdeCPUs = []string{ | 
|  | "p4p", // Pentium4 Prescott | 
|  | "mrm", // Merom | 
|  | "pnr", // Penryn | 
|  | "nhm", // Nehalem | 
|  | "wsm", // Westmere | 
|  | "snb", // Sandy Bridge | 
|  | "ivb", // Ivy Bridge | 
|  | "hsw", // Haswell | 
|  | "bdw", // Broadwell | 
|  | "slt", // Saltwell | 
|  | "slm", // Silvermont | 
|  | "glm", // Goldmont | 
|  | "glp", // Goldmont Plus | 
|  | "tnt", // Tremont | 
|  | "skl", // Skylake | 
|  | "cnl", // Cannon Lake | 
|  | "icl", // Ice Lake | 
|  | "skx", // Skylake server | 
|  | "clx", // Cascade Lake | 
|  | "cpx", // Cooper Lake | 
|  | "icx", // Ice Lake server | 
|  | "knl", // Knights landing | 
|  | "knm", // Knights mill | 
|  | "tgl", // Tiger Lake | 
|  | } | 
|  |  | 
|  | var armCPUs = []string{ | 
|  | "none",   // No support for any ARM extensions. | 
|  | "neon",   // Support for NEON. | 
|  | "crypto", // Support for NEON and crypto extensions. | 
|  | } | 
|  |  | 
|  | func valgrindOf(dbAttach bool, path string, args ...string) *exec.Cmd { | 
|  | valgrindArgs := []string{"--error-exitcode=99", "--track-origins=yes", "--leak-check=full", "--quiet"} | 
|  | if dbAttach { | 
|  | valgrindArgs = append(valgrindArgs, "--db-attach=yes", "--db-command=xterm -e gdb -nw %f %p") | 
|  | } | 
|  | valgrindArgs = append(valgrindArgs, path) | 
|  | valgrindArgs = append(valgrindArgs, args...) | 
|  |  | 
|  | return exec.Command("valgrind", valgrindArgs...) | 
|  | } | 
|  |  | 
|  | func callgrindOf(path string, args ...string) *exec.Cmd { | 
|  | valgrindArgs := []string{"-q", "--tool=callgrind", "--dump-instr=yes", "--collect-jumps=yes", "--callgrind-out-file=" + *buildDir + "/callgrind/callgrind.out.%p"} | 
|  | valgrindArgs = append(valgrindArgs, path) | 
|  | valgrindArgs = append(valgrindArgs, args...) | 
|  |  | 
|  | return exec.Command("valgrind", valgrindArgs...) | 
|  | } | 
|  |  | 
|  | func gdbOf(path string, args ...string) *exec.Cmd { | 
|  | xtermArgs := []string{"-e", "gdb", "--args"} | 
|  | xtermArgs = append(xtermArgs, path) | 
|  | xtermArgs = append(xtermArgs, args...) | 
|  |  | 
|  | return exec.Command("xterm", xtermArgs...) | 
|  | } | 
|  |  | 
|  | func sdeOf(cpu, path string, args ...string) *exec.Cmd { | 
|  | sdeArgs := []string{"-" + cpu} | 
|  | // The kernel's vdso code for gettimeofday sometimes uses the RDTSCP | 
|  | // instruction. Although SDE has a -chip_check_vsyscall flag that | 
|  | // excludes such code by default, it does not seem to work. Instead, | 
|  | // pass the -chip_check_exe_only flag which retains test coverage when | 
|  | // statically linked and excludes the vdso. | 
|  | if cpu == "p4p" || cpu == "pnr" || cpu == "mrm" || cpu == "slt" { | 
|  | sdeArgs = append(sdeArgs, "-chip_check_exe_only") | 
|  | } | 
|  | sdeArgs = append(sdeArgs, "--", path) | 
|  | sdeArgs = append(sdeArgs, args...) | 
|  | return exec.Command(*sdePath, sdeArgs...) | 
|  | } | 
|  |  | 
|  | var ( | 
|  | errMoreMallocs = errors.New("child process did not exhaust all allocation calls") | 
|  | errTestSkipped = errors.New("test was skipped") | 
|  | ) | 
|  |  | 
|  | func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) { | 
|  | prog := path.Join(*buildDir, test.Cmd[0]) | 
|  | args := append([]string{}, test.Cmd[1:]...) | 
|  | if *simulateARMCPUs && test.cpu != "" { | 
|  | args = append(args, "--cpu="+test.cpu) | 
|  | } | 
|  | if *useSDE { | 
|  | // SDE is neither compatible with the unwind tester nor automatically | 
|  | // detected. | 
|  | args = append(args, "--no_unwind_tests") | 
|  | } | 
|  | var cmd *exec.Cmd | 
|  | if *useValgrind { | 
|  | cmd = valgrindOf(false, prog, args...) | 
|  | } else if *useCallgrind { | 
|  | cmd = callgrindOf(prog, args...) | 
|  | } else if *useGDB { | 
|  | cmd = gdbOf(prog, args...) | 
|  | } else if *useSDE { | 
|  | cmd = sdeOf(test.cpu, prog, args...) | 
|  | } else { | 
|  | cmd = exec.Command(prog, args...) | 
|  | } | 
|  | if test.Env != nil || test.numShards != 0 { | 
|  | cmd.Env = make([]string, len(os.Environ())) | 
|  | copy(cmd.Env, os.Environ()) | 
|  | } | 
|  | if test.Env != nil { | 
|  | cmd.Env = append(cmd.Env, test.Env...) | 
|  | } | 
|  | if test.numShards != 0 { | 
|  | cmd.Env = append(cmd.Env, fmt.Sprintf("GTEST_SHARD_INDEX=%d", test.shard)) | 
|  | cmd.Env = append(cmd.Env, fmt.Sprintf("GTEST_TOTAL_SHARDS=%d", test.numShards)) | 
|  | } | 
|  | var outBuf bytes.Buffer | 
|  | cmd.Stdout = &outBuf | 
|  | cmd.Stderr = &outBuf | 
|  | if mallocNumToFail >= 0 { | 
|  | cmd.Env = os.Environ() | 
|  | cmd.Env = append(cmd.Env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10)) | 
|  | if *mallocTestDebug { | 
|  | cmd.Env = append(cmd.Env, "MALLOC_ABORT_ON_FAIL=1") | 
|  | } | 
|  | cmd.Env = append(cmd.Env, "_MALLOC_CHECK=1") | 
|  | } | 
|  |  | 
|  | if err := cmd.Start(); err != nil { | 
|  | return false, err | 
|  | } | 
|  | if err := cmd.Wait(); err != nil { | 
|  | if exitError, ok := err.(*exec.ExitError); ok { | 
|  | switch exitError.Sys().(syscall.WaitStatus).ExitStatus() { | 
|  | case 88: | 
|  | return false, errMoreMallocs | 
|  | case 89: | 
|  | fmt.Print(string(outBuf.Bytes())) | 
|  | return false, errTestSkipped | 
|  | } | 
|  | } | 
|  | fmt.Print(string(outBuf.Bytes())) | 
|  | return false, err | 
|  | } | 
|  |  | 
|  | // Account for Windows line-endings. | 
|  | stdout := bytes.Replace(outBuf.Bytes(), []byte("\r\n"), []byte("\n"), -1) | 
|  |  | 
|  | if bytes.HasSuffix(stdout, []byte("PASS\n")) && | 
|  | (len(stdout) == 5 || stdout[len(stdout)-6] == '\n') { | 
|  | return true, nil | 
|  | } | 
|  |  | 
|  | // Also accept a googletest-style pass line. This is left here in | 
|  | // transition until the tests are all converted and this script made | 
|  | // unnecessary. | 
|  | if bytes.Contains(stdout, []byte("\n[  PASSED  ]")) { | 
|  | return true, nil | 
|  | } | 
|  |  | 
|  | fmt.Print(string(outBuf.Bytes())) | 
|  | return false, nil | 
|  | } | 
|  |  | 
|  | func runTest(test test) (bool, error) { | 
|  | if *mallocTest < 0 { | 
|  | return runTestOnce(test, -1) | 
|  | } | 
|  |  | 
|  | for mallocNumToFail := int64(*mallocTest); ; mallocNumToFail++ { | 
|  | if passed, err := runTestOnce(test, mallocNumToFail); err != errMoreMallocs { | 
|  | if err != nil { | 
|  | err = fmt.Errorf("at malloc %d: %s", mallocNumToFail, err) | 
|  | } | 
|  | return passed, err | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // setWorkingDirectory walks up directories as needed until the current working | 
|  | // directory is the top of a BoringSSL checkout. | 
|  | func setWorkingDirectory() { | 
|  | for i := 0; i < 64; i++ { | 
|  | if _, err := os.Stat("BUILDING.md"); err == nil { | 
|  | return | 
|  | } | 
|  | os.Chdir("..") | 
|  | } | 
|  |  | 
|  | panic("Couldn't find BUILDING.md in a parent directory!") | 
|  | } | 
|  |  | 
|  | func worker(tests <-chan test, results chan<- result, done *sync.WaitGroup) { | 
|  | defer done.Done() | 
|  | for test := range tests { | 
|  | passed, err := runTest(test) | 
|  | results <- result{test, passed, err} | 
|  | } | 
|  | } | 
|  |  | 
|  | func (t test) shortName() string { | 
|  | return strings.Join(t.Cmd, " ") + t.shardMsg() + t.cpuMsg() + t.envMsg() | 
|  | } | 
|  |  | 
|  | func SpaceIf(returnSpace bool) string { | 
|  | if !returnSpace { | 
|  | return "" | 
|  | } | 
|  | return " " | 
|  | } | 
|  |  | 
|  | func (t test) longName() string { | 
|  | return strings.Join(t.Env, " ") + SpaceIf(len(t.Env) != 0) + strings.Join(t.Cmd, " ") + t.shardMsg() + t.cpuMsg() | 
|  | } | 
|  |  | 
|  | func (t test) shardMsg() string { | 
|  | if t.numShards == 0 { | 
|  | return "" | 
|  | } | 
|  |  | 
|  | return fmt.Sprintf(" [shard %d/%d]", t.shard+1, t.numShards) | 
|  | } | 
|  |  | 
|  | func (t test) cpuMsg() string { | 
|  | if len(t.cpu) == 0 { | 
|  | return "" | 
|  | } | 
|  |  | 
|  | return fmt.Sprintf(" (for CPU %q)", t.cpu) | 
|  | } | 
|  |  | 
|  | func (t test) envMsg() string { | 
|  | if len(t.Env) == 0 { | 
|  | return "" | 
|  | } | 
|  |  | 
|  | return " (custom environment)" | 
|  | } | 
|  |  | 
|  | func (t test) getGTestShards() ([]test, error) { | 
|  | if *numWorkers == 1 || !t.Shard { | 
|  | return []test{t}, nil | 
|  | } | 
|  |  | 
|  | shards := make([]test, *numWorkers) | 
|  | for i := range shards { | 
|  | shards[i] = t | 
|  | shards[i].shard = i | 
|  | shards[i].numShards = *numWorkers | 
|  | } | 
|  |  | 
|  | return shards, nil | 
|  | } | 
|  |  | 
|  | func main() { | 
|  | flag.Parse() | 
|  | setWorkingDirectory() | 
|  |  | 
|  | testCases, err := testconfig.ParseTestConfig("util/all_tests.json") | 
|  | if err != nil { | 
|  | fmt.Printf("Failed to parse input: %s\n", err) | 
|  | os.Exit(1) | 
|  | } | 
|  |  | 
|  | var wg sync.WaitGroup | 
|  | tests := make(chan test, *numWorkers) | 
|  | results := make(chan result, *numWorkers) | 
|  |  | 
|  | for i := 0; i < *numWorkers; i++ { | 
|  | wg.Add(1) | 
|  | go worker(tests, results, &wg) | 
|  | } | 
|  |  | 
|  | go func() { | 
|  | for _, baseTest := range testCases { | 
|  | test := test{Test: baseTest} | 
|  | if *useSDE { | 
|  | if test.SkipSDE { | 
|  | continue | 
|  | } | 
|  | // SDE generates plenty of tasks and gets slower | 
|  | // with additional sharding. | 
|  | for _, cpu := range sdeCPUs { | 
|  | testForCPU := test | 
|  | testForCPU.cpu = cpu | 
|  | tests <- testForCPU | 
|  | } | 
|  | } else if *simulateARMCPUs { | 
|  | // This mode is run instead of the default path, | 
|  | // so also include the native flow. | 
|  | tests <- test | 
|  | for _, cpu := range armCPUs { | 
|  | testForCPU := test | 
|  | testForCPU.cpu = cpu | 
|  | tests <- testForCPU | 
|  | } | 
|  | } else { | 
|  | shards, err := test.getGTestShards() | 
|  | if err != nil { | 
|  | fmt.Printf("Error listing tests: %s\n", err) | 
|  | os.Exit(1) | 
|  | } | 
|  | for _, shard := range shards { | 
|  | tests <- shard | 
|  | } | 
|  | } | 
|  | } | 
|  | close(tests) | 
|  |  | 
|  | wg.Wait() | 
|  | close(results) | 
|  | }() | 
|  |  | 
|  | testOutput := testresult.NewResults() | 
|  | var failed, skipped []test | 
|  | for testResult := range results { | 
|  | test := testResult.Test | 
|  | args := test.Cmd | 
|  |  | 
|  | if testResult.Error == errTestSkipped { | 
|  | fmt.Printf("%s\n", test.longName()) | 
|  | fmt.Printf("%s was skipped\n", args[0]) | 
|  | skipped = append(skipped, test) | 
|  | testOutput.AddSkip(test.longName()) | 
|  | } else if testResult.Error != nil { | 
|  | fmt.Printf("%s\n", test.longName()) | 
|  | fmt.Printf("%s failed to complete: %s\n", args[0], testResult.Error) | 
|  | failed = append(failed, test) | 
|  | testOutput.AddResult(test.longName(), "CRASH") | 
|  | } else if !testResult.Passed { | 
|  | fmt.Printf("%s\n", test.longName()) | 
|  | fmt.Printf("%s failed to print PASS on the last line.\n", args[0]) | 
|  | failed = append(failed, test) | 
|  | testOutput.AddResult(test.longName(), "FAIL") | 
|  | } else { | 
|  | fmt.Printf("%s\n", test.shortName()) | 
|  | testOutput.AddResult(test.longName(), "PASS") | 
|  | } | 
|  | } | 
|  |  | 
|  | if *jsonOutput != "" { | 
|  | if err := testOutput.WriteToFile(*jsonOutput); err != nil { | 
|  | fmt.Fprintf(os.Stderr, "Error: %s\n", err) | 
|  | } | 
|  | } | 
|  |  | 
|  | if len(skipped) > 0 { | 
|  | fmt.Printf("\n%d of %d tests were skipped:\n", len(skipped), len(testCases)) | 
|  | for _, test := range skipped { | 
|  | fmt.Printf("\t%s\n", test.shortName()) | 
|  | } | 
|  | } | 
|  |  | 
|  | if len(failed) > 0 { | 
|  | fmt.Printf("\n%d of %d tests failed:\n", len(failed), len(testCases)) | 
|  | for _, test := range failed { | 
|  | fmt.Printf("\t%s\n", test.shortName()) | 
|  | } | 
|  | os.Exit(1) | 
|  | } | 
|  |  | 
|  | fmt.Printf("\nAll tests passed!\n") | 
|  | } |