• 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	"encoding/json"
21	"errors"
22	"flag"
23	"fmt"
24	"math/rand"
25	"os"
26	"os/exec"
27	"path"
28	"runtime"
29	"strconv"
30	"strings"
31	"sync"
32	"syscall"
33
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.GOARCH == "arm" || runtime.GOARCH == "arm64")
55}
56
57type test struct {
58	args             []string
59	shard, numShards int
60	// cpu, if not empty, contains a code to simulate. For SDE, run `sde64
61	// -help` to get a list of these codes. For ARM, see gtest_main.cc for
62	// the supported values.
63	cpu string
64}
65
66type result struct {
67	Test   test
68	Passed bool
69	Error  error
70}
71
72// sdeCPUs contains a list of CPU code that we run all tests under when *useSDE
73// is true.
74var sdeCPUs = []string{
75	"p4p", // Pentium4 Prescott
76	"mrm", // Merom
77	"pnr", // Penryn
78	"nhm", // Nehalem
79	"wsm", // Westmere
80	"snb", // Sandy Bridge
81	"ivb", // Ivy Bridge
82	"hsw", // Haswell
83	"bdw", // Broadwell
84	"skx", // Skylake Server
85	"skl", // Skylake Client
86	"cnl", // Cannonlake
87	"knl", // Knights Landing
88	"slt", // Saltwell
89	"slm", // Silvermont
90	"glm", // Goldmont
91	"knm", // Knights Mill
92}
93
94var armCPUs = []string{
95	"none",   // No support for any ARM extensions.
96	"neon",   // Support for NEON.
97	"crypto", // Support for NEON and crypto extensions.
98}
99
100func valgrindOf(dbAttach bool, path string, args ...string) *exec.Cmd {
101	valgrindArgs := []string{"--error-exitcode=99", "--track-origins=yes", "--leak-check=full", "--quiet"}
102	if dbAttach {
103		valgrindArgs = append(valgrindArgs, "--db-attach=yes", "--db-command=xterm -e gdb -nw %f %p")
104	}
105	valgrindArgs = append(valgrindArgs, path)
106	valgrindArgs = append(valgrindArgs, args...)
107
108	return exec.Command("valgrind", valgrindArgs...)
109}
110
111func callgrindOf(path string, args ...string) *exec.Cmd {
112	valgrindArgs := []string{"-q", "--tool=callgrind", "--dump-instr=yes", "--collect-jumps=yes", "--callgrind-out-file=" + *buildDir + "/callgrind/callgrind.out.%p"}
113	valgrindArgs = append(valgrindArgs, path)
114	valgrindArgs = append(valgrindArgs, args...)
115
116	return exec.Command("valgrind", valgrindArgs...)
117}
118
119func gdbOf(path string, args ...string) *exec.Cmd {
120	xtermArgs := []string{"-e", "gdb", "--args"}
121	xtermArgs = append(xtermArgs, path)
122	xtermArgs = append(xtermArgs, args...)
123
124	return exec.Command("xterm", xtermArgs...)
125}
126
127func sdeOf(cpu, path string, args ...string) *exec.Cmd {
128	sdeArgs := []string{"-" + cpu}
129	// The kernel's vdso code for gettimeofday sometimes uses the RDTSCP
130	// instruction. Although SDE has a -chip_check_vsyscall flag that
131	// excludes such code by default, it does not seem to work. Instead,
132	// pass the -chip_check_exe_only flag which retains test coverage when
133	// statically linked and excludes the vdso.
134	if cpu == "p4p" || cpu == "pnr" || cpu == "mrm" || cpu == "slt" {
135		sdeArgs = append(sdeArgs, "-chip_check_exe_only")
136	}
137	sdeArgs = append(sdeArgs, "--", path)
138	sdeArgs = append(sdeArgs, args...)
139	return exec.Command(*sdePath, sdeArgs...)
140}
141
142var (
143	errMoreMallocs = errors.New("child process did not exhaust all allocation calls")
144	errTestSkipped = errors.New("test was skipped")
145)
146
147func runTestOnce(test test, mallocNumToFail int64) (passed bool, err error) {
148	prog := path.Join(*buildDir, test.args[0])
149	args := append([]string{}, test.args[1:]...)
150	if *simulateARMCPUs && test.cpu != "" {
151		args = append(args, "--cpu=" + test.cpu)
152	}
153	if *useSDE {
154		// SDE is neither compatible with the unwind tester nor automatically
155		// detected.
156		args = append(args, "--no_unwind_tests")
157	}
158	var cmd *exec.Cmd
159	if *useValgrind {
160		cmd = valgrindOf(false, prog, args...)
161	} else if *useCallgrind {
162		cmd = callgrindOf(prog, args...)
163	} else if *useGDB {
164		cmd = gdbOf(prog, args...)
165	} else if *useSDE {
166		cmd = sdeOf(test.cpu, prog, args...)
167	} else {
168		cmd = exec.Command(prog, args...)
169	}
170	var outBuf bytes.Buffer
171	cmd.Stdout = &outBuf
172	cmd.Stderr = &outBuf
173	if mallocNumToFail >= 0 {
174		cmd.Env = os.Environ()
175		cmd.Env = append(cmd.Env, "MALLOC_NUMBER_TO_FAIL="+strconv.FormatInt(mallocNumToFail, 10))
176		if *mallocTestDebug {
177			cmd.Env = append(cmd.Env, "MALLOC_ABORT_ON_FAIL=1")
178		}
179		cmd.Env = append(cmd.Env, "_MALLOC_CHECK=1")
180	}
181
182	if err := cmd.Start(); err != nil {
183		return false, err
184	}
185	if err := cmd.Wait(); err != nil {
186		if exitError, ok := err.(*exec.ExitError); ok {
187			switch exitError.Sys().(syscall.WaitStatus).ExitStatus() {
188			case 88:
189				return false, errMoreMallocs
190			case 89:
191				fmt.Print(string(outBuf.Bytes()))
192				return false, errTestSkipped
193			}
194		}
195		fmt.Print(string(outBuf.Bytes()))
196		return false, err
197	}
198
199	// Account for Windows line-endings.
200	stdout := bytes.Replace(outBuf.Bytes(), []byte("\r\n"), []byte("\n"), -1)
201
202	if bytes.HasSuffix(stdout, []byte("PASS\n")) &&
203		(len(stdout) == 5 || stdout[len(stdout)-6] == '\n') {
204		return true, nil
205	}
206
207	// Also accept a googletest-style pass line. This is left here in
208	// transition until the tests are all converted and this script made
209	// unnecessary.
210	if bytes.Contains(stdout, []byte("\n[  PASSED  ]")) {
211		return true, nil
212	}
213
214	fmt.Print(string(outBuf.Bytes()))
215	return false, nil
216}
217
218func runTest(test test) (bool, error) {
219	if *mallocTest < 0 {
220		return runTestOnce(test, -1)
221	}
222
223	for mallocNumToFail := int64(*mallocTest); ; mallocNumToFail++ {
224		if passed, err := runTestOnce(test, mallocNumToFail); err != errMoreMallocs {
225			if err != nil {
226				err = fmt.Errorf("at malloc %d: %s", mallocNumToFail, err)
227			}
228			return passed, err
229		}
230	}
231}
232
233// setWorkingDirectory walks up directories as needed until the current working
234// directory is the top of a BoringSSL checkout.
235func setWorkingDirectory() {
236	for i := 0; i < 64; i++ {
237		if _, err := os.Stat("BUILDING.md"); err == nil {
238			return
239		}
240		os.Chdir("..")
241	}
242
243	panic("Couldn't find BUILDING.md in a parent directory!")
244}
245
246func parseTestConfig(filename string) ([]test, error) {
247	in, err := os.Open(filename)
248	if err != nil {
249		return nil, err
250	}
251	defer in.Close()
252
253	decoder := json.NewDecoder(in)
254	var testArgs [][]string
255	if err := decoder.Decode(&testArgs); err != nil {
256		return nil, err
257	}
258
259	var result []test
260	for _, args := range testArgs {
261		result = append(result, test{args: args})
262	}
263	return result, nil
264}
265
266func worker(tests <-chan test, results chan<- result, done *sync.WaitGroup) {
267	defer done.Done()
268	for test := range tests {
269		passed, err := runTest(test)
270		results <- result{test, passed, err}
271	}
272}
273
274func (t test) shortName() string {
275	return t.args[0] + t.shardMsg() + t.cpuMsg()
276}
277
278func (t test) longName() string {
279	return strings.Join(t.args, " ") + 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) getGTestShards() ([]test, error) {
299	if *numWorkers == 1 || len(t.args) != 1 {
300		return []test{t}, nil
301	}
302
303	// Only shard the three GTest-based tests.
304	if t.args[0] != "crypto/crypto_test" && t.args[0] != "ssl/ssl_test" && t.args[0] != "decrepit/decrepit_test" {
305		return []test{t}, nil
306	}
307
308	prog := path.Join(*buildDir, t.args[0])
309	cmd := exec.Command(prog, "--gtest_list_tests")
310	var stdout bytes.Buffer
311	cmd.Stdout = &stdout
312	if err := cmd.Start(); err != nil {
313		return nil, err
314	}
315	if err := cmd.Wait(); err != nil {
316		return nil, err
317	}
318
319	var group string
320	var tests []string
321	scanner := bufio.NewScanner(&stdout)
322	for scanner.Scan() {
323		line := scanner.Text()
324
325		// Remove the parameter comment and trailing space.
326		if idx := strings.Index(line, "#"); idx >= 0 {
327			line = line[:idx]
328		}
329		line = strings.TrimSpace(line)
330		if len(line) == 0 {
331			continue
332		}
333
334		if line[len(line)-1] == '.' {
335			group = line
336			continue
337		}
338
339		if len(group) == 0 {
340			return nil, fmt.Errorf("found test case %q without group", line)
341		}
342		tests = append(tests, group+line)
343	}
344
345	const testsPerShard = 20
346	if len(tests) <= testsPerShard {
347		return []test{t}, nil
348	}
349
350	// Slow tests which process large test vector files tend to be grouped
351	// together, so shuffle the order.
352	shuffled := make([]string, len(tests))
353	perm := rand.Perm(len(tests))
354	for i, j := range perm {
355		shuffled[i] = tests[j]
356	}
357
358	var shards []test
359	for i := 0; i < len(shuffled); i += testsPerShard {
360		n := len(shuffled) - i
361		if n > testsPerShard {
362			n = testsPerShard
363		}
364		shard := t
365		shard.args = []string{shard.args[0], "--gtest_filter=" + strings.Join(shuffled[i:i+n], ":")}
366		shard.shard = len(shards)
367		shards = append(shards, shard)
368	}
369
370	for i := range shards {
371		shards[i].numShards = len(shards)
372	}
373
374	return shards, nil
375}
376
377func main() {
378	flag.Parse()
379	setWorkingDirectory()
380
381	testCases, err := parseTestConfig("util/all_tests.json")
382	if err != nil {
383		fmt.Printf("Failed to parse input: %s\n", err)
384		os.Exit(1)
385	}
386
387	var wg sync.WaitGroup
388	tests := make(chan test, *numWorkers)
389	results := make(chan result, *numWorkers)
390
391	for i := 0; i < *numWorkers; i++ {
392		wg.Add(1)
393		go worker(tests, results, &wg)
394	}
395
396	go func() {
397		for _, test := range testCases {
398			if *useSDE {
399				// SDE generates plenty of tasks and gets slower
400				// with additional sharding.
401				for _, cpu := range sdeCPUs {
402					testForCPU := test
403					testForCPU.cpu = cpu
404					tests <- testForCPU
405				}
406			} else if *simulateARMCPUs {
407				// This mode is run instead of the default path,
408				// so also include the native flow.
409				tests <- test
410				for _, cpu := range armCPUs {
411					testForCPU := test
412					testForCPU.cpu = cpu
413					tests <- testForCPU
414				}
415			} else {
416				shards, err := test.getGTestShards()
417				if err != nil {
418					fmt.Printf("Error listing tests: %s\n", err)
419					os.Exit(1)
420				}
421				for _, shard := range shards {
422					tests <- shard
423				}
424			}
425		}
426		close(tests)
427
428		wg.Wait()
429		close(results)
430	}()
431
432	testOutput := testresult.NewResults()
433	var failed, skipped []test
434	for testResult := range results {
435		test := testResult.Test
436		args := test.args
437
438		if testResult.Error == errTestSkipped {
439			fmt.Printf("%s\n", test.longName())
440			fmt.Printf("%s was skipped\n", args[0])
441			skipped = append(skipped, test)
442			testOutput.AddSkip(test.longName())
443		} else if testResult.Error != nil {
444			fmt.Printf("%s\n", test.longName())
445			fmt.Printf("%s failed to complete: %s\n", args[0], testResult.Error)
446			failed = append(failed, test)
447			testOutput.AddResult(test.longName(), "CRASH")
448		} else if !testResult.Passed {
449			fmt.Printf("%s\n", test.longName())
450			fmt.Printf("%s failed to print PASS on the last line.\n", args[0])
451			failed = append(failed, test)
452			testOutput.AddResult(test.longName(), "FAIL")
453		} else {
454			fmt.Printf("%s\n", test.shortName())
455			testOutput.AddResult(test.longName(), "PASS")
456		}
457	}
458
459	if *jsonOutput != "" {
460		if err := testOutput.WriteToFile(*jsonOutput); err != nil {
461			fmt.Fprintf(os.Stderr, "Error: %s\n", err)
462		}
463	}
464
465	if len(skipped) > 0 {
466		fmt.Printf("\n%d of %d tests were skipped:\n", len(skipped), len(testCases))
467		for _, test := range skipped {
468			fmt.Printf("\t%s%s\n", strings.Join(test.args, " "), test.cpuMsg())
469		}
470	}
471
472	if len(failed) > 0 {
473		fmt.Printf("\n%d of %d tests failed:\n", len(failed), len(testCases))
474		for _, test := range failed {
475			fmt.Printf("\t%s%s\n", strings.Join(test.args, " "), test.cpuMsg())
476		}
477		os.Exit(1)
478	}
479
480	fmt.Printf("\nAll tests passed!\n")
481}
482