• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package main
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"io/ioutil"
12	"os"
13	"os/exec"
14	"path/filepath"
15	"regexp"
16	"strings"
17	"testing"
18)
19
20const mainCc = "main.cc"
21const clangAndroid = "./clang"
22const clangX86_64 = "./x86_64-cros-linux-gnu-clang"
23const gccX86_64 = "./x86_64-cros-linux-gnu-gcc"
24const gccX86_64Eabi = "./x86_64-cros-eabi-gcc"
25const gccArmV7 = "./armv7m-cros-linux-gnu-gcc"
26const gccArmV7Eabi = "./armv7m-cros-eabi-gcc"
27const gccArmV8 = "./armv8m-cros-linux-gnu-gcc"
28const gccArmV8Eabi = "./armv8m-cros-eabi-gcc"
29
30type testContext struct {
31	t            *testing.T
32	wd           string
33	tempDir      string
34	env          []string
35	cfg          *config
36	inputCmd     *command
37	lastCmd      *command
38	cmdCount     int
39	cmdMock      func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error
40	stdinBuffer  bytes.Buffer
41	stdoutBuffer bytes.Buffer
42	stderrBuffer bytes.Buffer
43}
44
45func withTestContext(t *testing.T, work func(ctx *testContext)) {
46	t.Parallel()
47	tempDir, err := ioutil.TempDir("", "compiler_wrapper")
48	if err != nil {
49		t.Fatalf("Unable to create the temp dir. Error: %s", err)
50	}
51	defer os.RemoveAll(tempDir)
52
53	ctx := testContext{
54		t:       t,
55		wd:      tempDir,
56		tempDir: tempDir,
57		env:     nil,
58		cfg:     &config{},
59	}
60	ctx.updateConfig(&config{})
61
62	work(&ctx)
63}
64
65var _ env = (*testContext)(nil)
66
67func (ctx *testContext) getenv(key string) (string, bool) {
68	for i := len(ctx.env) - 1; i >= 0; i-- {
69		entry := ctx.env[i]
70		if strings.HasPrefix(entry, key+"=") {
71			return entry[len(key)+1:], true
72		}
73	}
74	return "", false
75}
76
77func (ctx *testContext) environ() []string {
78	return ctx.env
79}
80
81func (ctx *testContext) getwd() string {
82	return ctx.wd
83}
84
85func (ctx *testContext) stdin() io.Reader {
86	return &ctx.stdinBuffer
87}
88
89func (ctx *testContext) stdout() io.Writer {
90	return &ctx.stdoutBuffer
91}
92
93func (ctx *testContext) stdoutString() string {
94	return ctx.stdoutBuffer.String()
95}
96
97func (ctx *testContext) stderr() io.Writer {
98	return &ctx.stderrBuffer
99}
100
101func (ctx *testContext) stderrString() string {
102	return ctx.stderrBuffer.String()
103}
104
105func (ctx *testContext) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
106	ctx.cmdCount++
107	ctx.lastCmd = cmd
108	if ctx.cmdMock != nil {
109		return ctx.cmdMock(cmd, stdin, stdout, stderr)
110	}
111	return nil
112}
113
114func (ctx *testContext) exec(cmd *command) error {
115	ctx.cmdCount++
116	ctx.lastCmd = cmd
117	if ctx.cmdMock != nil {
118		return ctx.cmdMock(cmd, ctx.stdin(), ctx.stdout(), ctx.stderr())
119	}
120	return nil
121}
122
123func (ctx *testContext) must(exitCode int) *command {
124	if exitCode != 0 {
125		ctx.t.Fatalf("expected no error, but got exit code %d. Stderr: %s",
126			exitCode, ctx.stderrString())
127	}
128	return ctx.lastCmd
129}
130
131func (ctx *testContext) mustFail(exitCode int) string {
132	if exitCode == 0 {
133		ctx.t.Fatalf("expected an error, but got none")
134	}
135	return ctx.stderrString()
136}
137
138func (ctx *testContext) updateConfig(cfg *config) {
139	*ctx.cfg = *cfg
140	ctx.cfg.newWarningsDir = filepath.Join(ctx.tempDir, "fatal_clang_warnings")
141}
142
143func (ctx *testContext) newCommand(path string, args ...string) *command {
144	// Create an empty wrapper at the given path.
145	// Needed as we are resolving symlinks which stats the wrapper file.
146	ctx.writeFile(path, "")
147	return &command{
148		Path: path,
149		Args: args,
150	}
151}
152
153func (ctx *testContext) writeFile(fullFileName string, fileContent string) {
154	if !filepath.IsAbs(fullFileName) {
155		fullFileName = filepath.Join(ctx.tempDir, fullFileName)
156	}
157	if err := os.MkdirAll(filepath.Dir(fullFileName), 0777); err != nil {
158		ctx.t.Fatal(err)
159	}
160	if err := ioutil.WriteFile(fullFileName, []byte(fileContent), 0777); err != nil {
161		ctx.t.Fatal(err)
162	}
163}
164
165func (ctx *testContext) symlink(oldname string, newname string) {
166	if !filepath.IsAbs(oldname) {
167		oldname = filepath.Join(ctx.tempDir, oldname)
168	}
169	if !filepath.IsAbs(newname) {
170		newname = filepath.Join(ctx.tempDir, newname)
171	}
172	if err := os.MkdirAll(filepath.Dir(newname), 0777); err != nil {
173		ctx.t.Fatal(err)
174	}
175	if err := os.Symlink(oldname, newname); err != nil {
176		ctx.t.Fatal(err)
177	}
178}
179
180func (ctx *testContext) readAllString(r io.Reader) string {
181	if r == nil {
182		return ""
183	}
184	bytes, err := ioutil.ReadAll(r)
185	if err != nil {
186		ctx.t.Fatal(err)
187	}
188	return string(bytes)
189}
190
191func verifyPath(cmd *command, expectedRegex string) error {
192	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
193	if !compiledRegex.MatchString(cmd.Path) {
194		return fmt.Errorf("path does not match %s. Actual %s", expectedRegex, cmd.Path)
195	}
196	return nil
197}
198
199func verifyArgCount(cmd *command, expectedCount int, expectedRegex string) error {
200	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
201	count := 0
202	for _, arg := range cmd.Args {
203		if compiledRegex.MatchString(arg) {
204			count++
205		}
206	}
207	if count != expectedCount {
208		return fmt.Errorf("expected %d matches for arg %s. All args: %s",
209			expectedCount, expectedRegex, cmd.Args)
210	}
211	return nil
212}
213
214func verifyArgOrder(cmd *command, expectedRegexes ...string) error {
215	compiledRegexes := []*regexp.Regexp{}
216	for _, regex := range expectedRegexes {
217		compiledRegexes = append(compiledRegexes, regexp.MustCompile(matchFullString(regex)))
218	}
219	expectedArgIndex := 0
220	for _, arg := range cmd.Args {
221		if expectedArgIndex == len(compiledRegexes) {
222			break
223		} else if compiledRegexes[expectedArgIndex].MatchString(arg) {
224			expectedArgIndex++
225		}
226	}
227	if expectedArgIndex != len(expectedRegexes) {
228		return fmt.Errorf("expected args %s in order. All args: %s",
229			expectedRegexes, cmd.Args)
230	}
231	return nil
232}
233
234func verifyEnvUpdate(cmd *command, expectedRegex string) error {
235	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
236	for _, update := range cmd.EnvUpdates {
237		if compiledRegex.MatchString(update) {
238			return nil
239		}
240	}
241	return fmt.Errorf("expected at least one match for env update %s. All env updates: %s",
242		expectedRegex, cmd.EnvUpdates)
243}
244
245func verifyNoEnvUpdate(cmd *command, expectedRegex string) error {
246	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
247	updates := cmd.EnvUpdates
248	for _, update := range updates {
249		if compiledRegex.MatchString(update) {
250			return fmt.Errorf("expected no match for env update %s. All env updates: %s",
251				expectedRegex, cmd.EnvUpdates)
252		}
253	}
254	return nil
255}
256
257func hasInternalError(stderr string) bool {
258	return strings.Contains(stderr, "Internal error")
259}
260
261func verifyInternalError(stderr string) error {
262	if !hasInternalError(stderr) {
263		return fmt.Errorf("expected an internal error. Got: %s", stderr)
264	}
265	if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); !ok {
266		return fmt.Errorf("expected a source line reference. Got: %s", stderr)
267	}
268	return nil
269}
270
271func verifyNonInternalError(stderr string, expectedRegex string) error {
272	if hasInternalError(stderr) {
273		return fmt.Errorf("expected a non internal error. Got: %s", stderr)
274	}
275	if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); ok {
276		return fmt.Errorf("expected no source line reference. Got: %s", stderr)
277	}
278	if ok, _ := regexp.MatchString(matchFullString(expectedRegex), strings.TrimSpace(stderr)); !ok {
279		return fmt.Errorf("expected stderr matching %s. Got: %s", expectedRegex, stderr)
280	}
281	return nil
282}
283
284func matchFullString(regex string) string {
285	return "^" + regex + "$"
286}
287
288func newExitCodeError(exitCode int) error {
289	// It's actually hard to create an error that represents a command
290	// with exit code. Using a real command instead.
291	tmpCmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("exit %d", exitCode))
292	return tmpCmd.Run()
293}
294