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 "bytes" 19 "encoding/json" 20 "flag" 21 "fmt" 22 "os" 23 "os/exec" 24 "path" 25 "strconv" 26 "strings" 27 "sync" 28 "syscall" 29 "time" 30) 31 32// TODO(davidben): Link tests with the malloc shim and port -malloc-test to this runner. 33 34var ( 35 useValgrind = flag.Bool("valgrind", false, "If true, run code under valgrind") 36 useCallgrind = flag.Bool("callgrind", false, "If true, run code under valgrind to generate callgrind traces.") 37 useGDB = flag.Bool("gdb", false, "If true, run BoringSSL code under gdb") 38 useSDE = flag.Bool("sde", false, "If true, run BoringSSL code under Intel's SDE for each supported chip") 39 buildDir = flag.String("build-dir", "build", "The build directory to run the tests from.") 40 numWorkers = flag.Int("num-workers", 1, "Runs the given number of workers when testing.") 41 jsonOutput = flag.String("json-output", "", "The file to output JSON results to.") 42 mallocTest = flag.Int64("malloc-test", -1, "If non-negative, run each test with each malloc in turn failing from the given number onwards.") 43 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.") 44) 45 46type test struct { 47 args []string 48 // cpu, if not empty, contains an Intel CPU code to simulate. Run 49 // `sde64 -help` to get a list of these codes. 50 cpu string 51} 52 53type result struct { 54 Test test 55 Passed bool 56 Error error 57} 58 59// testOutput is a representation of Chromium's JSON test result format. See 60// https://www.chromium.org/developers/the-json-test-results-format 61type testOutput struct { 62 Version int `json:"version"` 63 Interrupted bool `json:"interrupted"` 64 PathDelimiter string `json:"path_delimiter"` 65 SecondsSinceEpoch float64 `json:"seconds_since_epoch"` 66 NumFailuresByType map[string]int `json:"num_failures_by_type"` 67 Tests map[string]testResult `json:"tests"` 68} 69 70type testResult struct { 71 Actual string `json:"actual"` 72 Expected string `json:"expected"` 73 IsUnexpected bool `json:"is_unexpected"` 74} 75 76// sdeCPUs contains a list of CPU code that we run all tests under when *useSDE 77// is true. 78var sdeCPUs = []string{ 79 "p4p", // Pentium4 Prescott 80 "mrm", // Merom 81 "pnr", // Penryn 82 "nhm", // Nehalem 83 "wsm", // Westmere 84 "snb", // Sandy Bridge 85 "ivb", // Ivy Bridge 86 "hsw", // Haswell 87 "bdw", // Broadwell 88 "skx", // Skylake Server 89 "skl", // Skylake Client 90 "cnl", // Cannonlake 91 "knl", // Knights Landing 92 "slt", // Saltwell 93 "slm", // Silvermont 94 "glm", // Goldmont 95} 96 97func newTestOutput() *testOutput { 98 return &testOutput{ 99 Version: 3, 100 PathDelimiter: ".", 101 SecondsSinceEpoch: float64(time.Now().UnixNano()) / float64(time.Second/time.Nanosecond), 102 NumFailuresByType: make(map[string]int), 103 Tests: make(map[string]testResult), 104 } 105} 106 107func (t *testOutput) addResult(name, result string) { 108 if _, found := t.Tests[name]; found { 109 panic(name) 110 } 111 t.Tests[name] = testResult{ 112 Actual: result, 113 Expected: "PASS", 114 IsUnexpected: result != "PASS", 115 } 116 t.NumFailuresByType[result]++ 117} 118 119func (t *testOutput) writeTo(name string) error { 120 file, err := os.Create(name) 121 if err != nil { 122 return err 123 } 124 defer file.Close() 125 out, err := json.MarshalIndent(t, "", " ") 126 if err != nil { 127 return err 128 } 129 _, err = file.Write(out) 130 return err 131} 132 133func valgrindOf(dbAttach bool, path string, args ...string) *exec.Cmd { 134 valgrindArgs := []string{"--error-exitcode=99", "--track-origins=yes", "--leak-check=full", "--quiet"} 135 if dbAttach { 136 valgrindArgs = append(valgrindArgs, "--db-attach=yes", "--db-command=xterm -e gdb -nw %f %p") 137 } 138 valgrindArgs = append(valgrindArgs, path) 139 valgrindArgs = append(valgrindArgs, args...) 140 141 return exec.Command("valgrind", valgrindArgs...) 142} 143 144func callgrindOf(path string, args ...string) *exec.Cmd { 145 valgrindArgs := []string{"-q", "--tool=callgrind", "--dump-instr=yes", "--collect-jumps=yes", "--callgrind-out-file=" + *buildDir + "/callgrind/callgrind.out.%p"} 146 valgrindArgs = append(valgrindArgs, path) 147 valgrindArgs = append(valgrindArgs, args...) 148 149 return exec.Command("valgrind", valgrindArgs...) 150} 151 152func gdbOf(path string, args ...string) *exec.Cmd { 153 xtermArgs := []string{"-e", "gdb", "--args"} 154 xtermArgs = append(xtermArgs, path) 155 xtermArgs = append(xtermArgs, args...) 156 157 return exec.Command("xterm", xtermArgs...) 158} 159 160func sdeOf(cpu, path string, args ...string) *exec.Cmd { 161 sdeArgs := []string{"-" + cpu, "--", path} 162 sdeArgs = append(sdeArgs, args...) 163 return exec.Command("sde", sdeArgs...) 164} 165 166type moreMallocsError struct{} 167 168func (moreMallocsError) Error() string { 169 return "child process did not exhaust all allocation calls" 170} 171 172var errMoreMallocs = moreMallocsError{} 173 174func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) { 175 prog := path.Join(*buildDir, test.args[0]) 176 args := test.args[1:] 177 var cmd *exec.Cmd 178 if *useValgrind { 179 cmd = valgrindOf(false, prog, args...) 180 } else if *useCallgrind { 181 cmd = callgrindOf(prog, args...) 182 } else if *useGDB { 183 cmd = gdbOf(prog, args...) 184 } else if *useSDE { 185 cmd = sdeOf(test.cpu, prog, args...) 186 } else { 187 cmd = exec.Command(prog, args...) 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 if exitError.Sys().(syscall.WaitStatus).ExitStatus() == 88 { 207 return false, errMoreMallocs 208 } 209 } 210 fmt.Print(string(outBuf.Bytes())) 211 return false, err 212 } 213 214 // Account for Windows line-endings. 215 stdout := bytes.Replace(outBuf.Bytes(), []byte("\r\n"), []byte("\n"), -1) 216 217 if bytes.HasSuffix(stdout, []byte("PASS\n")) && 218 (len(stdout) == 5 || stdout[len(stdout)-6] == '\n') { 219 return true, nil 220 } 221 222 // Also accept a googletest-style pass line. This is left here in 223 // transition until the tests are all converted and this script made 224 // unnecessary. 225 if bytes.Contains(stdout, []byte("\n[ PASSED ]")) { 226 return true, nil 227 } 228 229 fmt.Print(string(outBuf.Bytes())) 230 return false, nil 231} 232 233func runTest(test test) (bool, error) { 234 if *mallocTest < 0 { 235 return runTestOnce(test, -1) 236 } 237 238 for mallocNumToFail := int64(*mallocTest); ; mallocNumToFail++ { 239 if passed, err := runTestOnce(test, mallocNumToFail); err != errMoreMallocs { 240 if err != nil { 241 err = fmt.Errorf("at malloc %d: %s", mallocNumToFail, err) 242 } 243 return passed, err 244 } 245 } 246} 247 248// shortTestName returns the short name of a test. Except for evp_test, it 249// assumes that any argument which ends in .txt is a path to a data file and not 250// relevant to the test's uniqueness. 251func shortTestName(test test) string { 252 var args []string 253 for _, arg := range test.args { 254 if test.args[0] == "crypto/evp/evp_test" || !strings.HasSuffix(arg, ".txt") { 255 args = append(args, arg) 256 } 257 } 258 return strings.Join(args, " ") + test.cpuMsg() 259} 260 261// setWorkingDirectory walks up directories as needed until the current working 262// directory is the top of a BoringSSL checkout. 263func setWorkingDirectory() { 264 for i := 0; i < 64; i++ { 265 if _, err := os.Stat("BUILDING.md"); err == nil { 266 return 267 } 268 os.Chdir("..") 269 } 270 271 panic("Couldn't find BUILDING.md in a parent directory!") 272} 273 274func parseTestConfig(filename string) ([]test, error) { 275 in, err := os.Open(filename) 276 if err != nil { 277 return nil, err 278 } 279 defer in.Close() 280 281 decoder := json.NewDecoder(in) 282 var testArgs [][]string 283 if err := decoder.Decode(&testArgs); err != nil { 284 return nil, err 285 } 286 287 var result []test 288 for _, args := range testArgs { 289 result = append(result, test{args: args}) 290 } 291 return result, nil 292} 293 294func worker(tests <-chan test, results chan<- result, done *sync.WaitGroup) { 295 defer done.Done() 296 for test := range tests { 297 passed, err := runTest(test) 298 results <- result{test, passed, err} 299 } 300} 301 302func (t test) cpuMsg() string { 303 if len(t.cpu) == 0 { 304 return "" 305 } 306 307 return fmt.Sprintf(" (for CPU %q)", t.cpu) 308} 309 310func main() { 311 flag.Parse() 312 setWorkingDirectory() 313 314 testCases, err := parseTestConfig("util/all_tests.json") 315 if err != nil { 316 fmt.Printf("Failed to parse input: %s\n", err) 317 os.Exit(1) 318 } 319 320 var wg sync.WaitGroup 321 tests := make(chan test, *numWorkers) 322 results := make(chan result, *numWorkers) 323 324 for i := 0; i < *numWorkers; i++ { 325 wg.Add(1) 326 go worker(tests, results, &wg) 327 } 328 329 go func() { 330 for _, test := range testCases { 331 if *useSDE { 332 for _, cpu := range sdeCPUs { 333 testForCPU := test 334 testForCPU.cpu = cpu 335 tests <- testForCPU 336 } 337 } else { 338 tests <- test 339 } 340 } 341 close(tests) 342 343 wg.Wait() 344 close(results) 345 }() 346 347 testOutput := newTestOutput() 348 var failed []test 349 for testResult := range results { 350 test := testResult.Test 351 args := test.args 352 353 fmt.Printf("%s%s\n", strings.Join(args, " "), test.cpuMsg()) 354 name := shortTestName(test) 355 if testResult.Error != nil { 356 fmt.Printf("%s failed to complete: %s\n", args[0], testResult.Error) 357 failed = append(failed, test) 358 testOutput.addResult(name, "CRASHED") 359 } else if !testResult.Passed { 360 fmt.Printf("%s failed to print PASS on the last line.\n", args[0]) 361 failed = append(failed, test) 362 testOutput.addResult(name, "FAIL") 363 } else { 364 testOutput.addResult(name, "PASS") 365 } 366 } 367 368 if *jsonOutput != "" { 369 if err := testOutput.writeTo(*jsonOutput); err != nil { 370 fmt.Fprintf(os.Stderr, "Error: %s\n", err) 371 } 372 } 373 374 if len(failed) > 0 { 375 fmt.Printf("\n%d of %d tests failed:\n", len(failed), len(testCases)) 376 for _, test := range failed { 377 fmt.Printf("\t%s%s\n", strings.Join(test.args, ""), test.cpuMsg()) 378 } 379 os.Exit(1) 380 } 381 382 fmt.Printf("\nAll tests passed!\n") 383} 384