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