Mirror of BoringSSL (grpc依赖)
https://boringssl.googlesource.com/boringssl
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
12 KiB
438 lines
12 KiB
/* 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. */ |
|
|
|
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 || len(t.Cmd) != 1 { |
|
return []test{t}, nil |
|
} |
|
|
|
// Only shard the three GTest-based tests. |
|
if t.Cmd[0] != "crypto/crypto_test" && t.Cmd[0] != "ssl/ssl_test" && t.Cmd[0] != "decrepit/decrepit_test" { |
|
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") |
|
}
|
|
|