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