xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/testutil_test.go (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li// Copyright 2019 The ChromiumOS Authors
2*760c253cSXin Li// Use of this source code is governed by a BSD-style license that can be
3*760c253cSXin Li// found in the LICENSE file.
4*760c253cSXin Li
5*760c253cSXin Lipackage main
6*760c253cSXin Li
7*760c253cSXin Liimport (
8*760c253cSXin Li	"bytes"
9*760c253cSXin Li	"fmt"
10*760c253cSXin Li	"io"
11*760c253cSXin Li	"io/ioutil"
12*760c253cSXin Li	"os"
13*760c253cSXin Li	"os/exec"
14*760c253cSXin Li	"path/filepath"
15*760c253cSXin Li	"regexp"
16*760c253cSXin Li	"strings"
17*760c253cSXin Li	"sync"
18*760c253cSXin Li	"syscall"
19*760c253cSXin Li	"testing"
20*760c253cSXin Li	"time"
21*760c253cSXin Li)
22*760c253cSXin Li
23*760c253cSXin Liconst (
24*760c253cSXin Li	mainCc           = "main.cc"
25*760c253cSXin Li	clangAndroid     = "./clang"
26*760c253cSXin Li	clangTidyAndroid = "./clang-tidy"
27*760c253cSXin Li	clangX86_64      = "./x86_64-cros-linux-gnu-clang"
28*760c253cSXin Li	gccX86_64        = "./x86_64-cros-linux-gnu-gcc"
29*760c253cSXin Li	gccX86_64Eabi    = "./x86_64-cros-eabi-gcc"
30*760c253cSXin Li	gccArmV7         = "./armv7m-cros-linux-gnu-gcc"
31*760c253cSXin Li	gccArmV7Eabi     = "./armv7m-cros-eabi-gcc"
32*760c253cSXin Li	gccArmV8         = "./armv8m-cros-linux-gnu-gcc"
33*760c253cSXin Li	gccArmV8Eabi     = "./armv8m-cros-eabi-gcc"
34*760c253cSXin Li)
35*760c253cSXin Li
36*760c253cSXin Litype testContext struct {
37*760c253cSXin Li	t            *testing.T
38*760c253cSXin Li	wd           string
39*760c253cSXin Li	tempDir      string
40*760c253cSXin Li	env          []string
41*760c253cSXin Li	cfg          *config
42*760c253cSXin Li	lastCmd      *command
43*760c253cSXin Li	cmdCount     int
44*760c253cSXin Li	cmdMock      func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error
45*760c253cSXin Li	stdinBuffer  bytes.Buffer
46*760c253cSXin Li	stdoutBuffer bytes.Buffer
47*760c253cSXin Li	stderrBuffer bytes.Buffer
48*760c253cSXin Li
49*760c253cSXin Li	umaskRestoreAction func()
50*760c253cSXin Li}
51*760c253cSXin Li
52*760c253cSXin Li// We have some tests which modify our umask, and other tests which depend upon the value of our
53*760c253cSXin Li// umask remaining consistent. This lock serializes those. Please use `NoteTestWritesToUmask()` and
54*760c253cSXin Li// `NoteTestDependsOnUmask()` on `testContext` rather than using this directly.
55*760c253cSXin Livar umaskModificationLock sync.RWMutex
56*760c253cSXin Li
57*760c253cSXin Lifunc withTestContext(t *testing.T, work func(ctx *testContext)) {
58*760c253cSXin Li	t.Parallel()
59*760c253cSXin Li	tempDir, err := ioutil.TempDir("", "compiler_wrapper")
60*760c253cSXin Li	if err != nil {
61*760c253cSXin Li		t.Fatalf("Unable to create the temp dir. Error: %s", err)
62*760c253cSXin Li	}
63*760c253cSXin Li	defer os.RemoveAll(tempDir)
64*760c253cSXin Li
65*760c253cSXin Li	ctx := testContext{
66*760c253cSXin Li		t:       t,
67*760c253cSXin Li		wd:      tempDir,
68*760c253cSXin Li		tempDir: tempDir,
69*760c253cSXin Li		env:     nil,
70*760c253cSXin Li		cfg:     &config{},
71*760c253cSXin Li	}
72*760c253cSXin Li	ctx.updateConfig(&config{})
73*760c253cSXin Li
74*760c253cSXin Li	defer ctx.maybeReleaseUmaskDependency()
75*760c253cSXin Li	work(&ctx)
76*760c253cSXin Li}
77*760c253cSXin Li
78*760c253cSXin Livar _ env = (*testContext)(nil)
79*760c253cSXin Li
80*760c253cSXin Lifunc (ctx *testContext) umask(mask int) (oldmask int) {
81*760c253cSXin Li	if ctx.umaskRestoreAction == nil {
82*760c253cSXin Li		panic("Umask operations requested in test without declaring a umask dependency")
83*760c253cSXin Li	}
84*760c253cSXin Li	return syscall.Umask(mask)
85*760c253cSXin Li}
86*760c253cSXin Li
87*760c253cSXin Lifunc (ctx *testContext) setArbitraryClangArtifactsDir() string {
88*760c253cSXin Li	d := filepath.Join(ctx.tempDir, "cros-artifacts")
89*760c253cSXin Li	ctx.env = append(ctx.env, crosArtifactsEnvVar+"="+d)
90*760c253cSXin Li	return d
91*760c253cSXin Li}
92*760c253cSXin Li
93*760c253cSXin Lifunc (ctx *testContext) initUmaskDependency(lockFn func(), unlockFn func()) {
94*760c253cSXin Li	if ctx.umaskRestoreAction != nil {
95*760c253cSXin Li		// Use a panic so we get a backtrace.
96*760c253cSXin Li		panic("Multiple notes of a test depending on the value of `umask` given -- tests " +
97*760c253cSXin Li			"are only allowed up to one.")
98*760c253cSXin Li	}
99*760c253cSXin Li
100*760c253cSXin Li	lockFn()
101*760c253cSXin Li	ctx.umaskRestoreAction = unlockFn
102*760c253cSXin Li}
103*760c253cSXin Li
104*760c253cSXin Lifunc (ctx *testContext) maybeReleaseUmaskDependency() {
105*760c253cSXin Li	if ctx.umaskRestoreAction != nil {
106*760c253cSXin Li		ctx.umaskRestoreAction()
107*760c253cSXin Li	}
108*760c253cSXin Li}
109*760c253cSXin Li
110*760c253cSXin Li// Note that the test depends on a stable value for the process' umask.
111*760c253cSXin Lifunc (ctx *testContext) NoteTestReadsFromUmask() {
112*760c253cSXin Li	ctx.initUmaskDependency(umaskModificationLock.RLock, umaskModificationLock.RUnlock)
113*760c253cSXin Li}
114*760c253cSXin Li
115*760c253cSXin Li// Note that the test modifies the process' umask. This implies a dependency on the process' umask,
116*760c253cSXin Li// so it's an error to call both NoteTestWritesToUmask and NoteTestReadsFromUmask from the same
117*760c253cSXin Li// test.
118*760c253cSXin Lifunc (ctx *testContext) NoteTestWritesToUmask() {
119*760c253cSXin Li	ctx.initUmaskDependency(umaskModificationLock.Lock, umaskModificationLock.Unlock)
120*760c253cSXin Li}
121*760c253cSXin Li
122*760c253cSXin Lifunc (ctx *testContext) getenv(key string) (string, bool) {
123*760c253cSXin Li	for i := len(ctx.env) - 1; i >= 0; i-- {
124*760c253cSXin Li		entry := ctx.env[i]
125*760c253cSXin Li		if strings.HasPrefix(entry, key+"=") {
126*760c253cSXin Li			return entry[len(key)+1:], true
127*760c253cSXin Li		}
128*760c253cSXin Li	}
129*760c253cSXin Li	return "", false
130*760c253cSXin Li}
131*760c253cSXin Li
132*760c253cSXin Lifunc (ctx *testContext) environ() []string {
133*760c253cSXin Li	return ctx.env
134*760c253cSXin Li}
135*760c253cSXin Li
136*760c253cSXin Lifunc (ctx *testContext) getwd() string {
137*760c253cSXin Li	return ctx.wd
138*760c253cSXin Li}
139*760c253cSXin Li
140*760c253cSXin Lifunc (ctx *testContext) stdin() io.Reader {
141*760c253cSXin Li	return &ctx.stdinBuffer
142*760c253cSXin Li}
143*760c253cSXin Li
144*760c253cSXin Lifunc (ctx *testContext) stdout() io.Writer {
145*760c253cSXin Li	return &ctx.stdoutBuffer
146*760c253cSXin Li}
147*760c253cSXin Li
148*760c253cSXin Lifunc (ctx *testContext) stdoutString() string {
149*760c253cSXin Li	return ctx.stdoutBuffer.String()
150*760c253cSXin Li}
151*760c253cSXin Li
152*760c253cSXin Lifunc (ctx *testContext) stderr() io.Writer {
153*760c253cSXin Li	return &ctx.stderrBuffer
154*760c253cSXin Li}
155*760c253cSXin Li
156*760c253cSXin Lifunc (ctx *testContext) stderrString() string {
157*760c253cSXin Li	return ctx.stderrBuffer.String()
158*760c253cSXin Li}
159*760c253cSXin Li
160*760c253cSXin Lifunc (ctx *testContext) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
161*760c253cSXin Li	ctx.cmdCount++
162*760c253cSXin Li	ctx.lastCmd = cmd
163*760c253cSXin Li	if ctx.cmdMock != nil {
164*760c253cSXin Li		return ctx.cmdMock(cmd, stdin, stdout, stderr)
165*760c253cSXin Li	}
166*760c253cSXin Li	return nil
167*760c253cSXin Li}
168*760c253cSXin Li
169*760c253cSXin Lifunc (ctx *testContext) runWithTimeout(cmd *command, duration time.Duration) error {
170*760c253cSXin Li	return ctx.exec(cmd)
171*760c253cSXin Li}
172*760c253cSXin Li
173*760c253cSXin Lifunc (ctx *testContext) exec(cmd *command) error {
174*760c253cSXin Li	ctx.cmdCount++
175*760c253cSXin Li	ctx.lastCmd = cmd
176*760c253cSXin Li	if ctx.cmdMock != nil {
177*760c253cSXin Li		return ctx.cmdMock(cmd, ctx.stdin(), ctx.stdout(), ctx.stderr())
178*760c253cSXin Li	}
179*760c253cSXin Li	return nil
180*760c253cSXin Li}
181*760c253cSXin Li
182*760c253cSXin Lifunc (ctx *testContext) must(exitCode int) *command {
183*760c253cSXin Li	if exitCode != 0 {
184*760c253cSXin Li		ctx.t.Fatalf("expected no error, but got exit code %d. Stderr: %s",
185*760c253cSXin Li			exitCode, ctx.stderrString())
186*760c253cSXin Li	}
187*760c253cSXin Li	return ctx.lastCmd
188*760c253cSXin Li}
189*760c253cSXin Li
190*760c253cSXin Lifunc (ctx *testContext) mustFail(exitCode int) string {
191*760c253cSXin Li	if exitCode == 0 {
192*760c253cSXin Li		ctx.t.Fatalf("expected an error, but got none")
193*760c253cSXin Li	}
194*760c253cSXin Li	return ctx.stderrString()
195*760c253cSXin Li}
196*760c253cSXin Li
197*760c253cSXin Lifunc (ctx *testContext) updateConfig(cfg *config) {
198*760c253cSXin Li	*ctx.cfg = *cfg
199*760c253cSXin Li}
200*760c253cSXin Li
201*760c253cSXin Lifunc (ctx *testContext) newCommand(path string, args ...string) *command {
202*760c253cSXin Li	// Create an empty wrapper at the given path.
203*760c253cSXin Li	// Needed as we are resolving symlinks which stats the wrapper file.
204*760c253cSXin Li	ctx.writeFile(path, "")
205*760c253cSXin Li	return &command{
206*760c253cSXin Li		Path: path,
207*760c253cSXin Li		Args: args,
208*760c253cSXin Li	}
209*760c253cSXin Li}
210*760c253cSXin Li
211*760c253cSXin Lifunc (ctx *testContext) writeFile(fullFileName string, fileContent string) {
212*760c253cSXin Li	if !filepath.IsAbs(fullFileName) {
213*760c253cSXin Li		fullFileName = filepath.Join(ctx.tempDir, fullFileName)
214*760c253cSXin Li	}
215*760c253cSXin Li	if err := os.MkdirAll(filepath.Dir(fullFileName), 0777); err != nil {
216*760c253cSXin Li		ctx.t.Fatal(err)
217*760c253cSXin Li	}
218*760c253cSXin Li	if err := ioutil.WriteFile(fullFileName, []byte(fileContent), 0777); err != nil {
219*760c253cSXin Li		ctx.t.Fatal(err)
220*760c253cSXin Li	}
221*760c253cSXin Li}
222*760c253cSXin Li
223*760c253cSXin Lifunc (ctx *testContext) symlink(oldname string, newname string) {
224*760c253cSXin Li	if !filepath.IsAbs(oldname) {
225*760c253cSXin Li		oldname = filepath.Join(ctx.tempDir, oldname)
226*760c253cSXin Li	}
227*760c253cSXin Li	if !filepath.IsAbs(newname) {
228*760c253cSXin Li		newname = filepath.Join(ctx.tempDir, newname)
229*760c253cSXin Li	}
230*760c253cSXin Li	if err := os.MkdirAll(filepath.Dir(newname), 0777); err != nil {
231*760c253cSXin Li		ctx.t.Fatal(err)
232*760c253cSXin Li	}
233*760c253cSXin Li	if err := os.Symlink(oldname, newname); err != nil {
234*760c253cSXin Li		ctx.t.Fatal(err)
235*760c253cSXin Li	}
236*760c253cSXin Li}
237*760c253cSXin Li
238*760c253cSXin Lifunc (ctx *testContext) readAllString(r io.Reader) string {
239*760c253cSXin Li	if r == nil {
240*760c253cSXin Li		return ""
241*760c253cSXin Li	}
242*760c253cSXin Li	bytes, err := ioutil.ReadAll(r)
243*760c253cSXin Li	if err != nil {
244*760c253cSXin Li		ctx.t.Fatal(err)
245*760c253cSXin Li	}
246*760c253cSXin Li	return string(bytes)
247*760c253cSXin Li}
248*760c253cSXin Li
249*760c253cSXin Lifunc verifyPath(cmd *command, expectedRegex string) error {
250*760c253cSXin Li	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
251*760c253cSXin Li	if !compiledRegex.MatchString(cmd.Path) {
252*760c253cSXin Li		return fmt.Errorf("path does not match %s. Actual %s", expectedRegex, cmd.Path)
253*760c253cSXin Li	}
254*760c253cSXin Li	return nil
255*760c253cSXin Li}
256*760c253cSXin Li
257*760c253cSXin Lifunc verifyArgCount(cmd *command, expectedCount int, expectedRegex string) error {
258*760c253cSXin Li	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
259*760c253cSXin Li	count := 0
260*760c253cSXin Li	for _, arg := range cmd.Args {
261*760c253cSXin Li		if compiledRegex.MatchString(arg) {
262*760c253cSXin Li			count++
263*760c253cSXin Li		}
264*760c253cSXin Li	}
265*760c253cSXin Li	if count != expectedCount {
266*760c253cSXin Li		return fmt.Errorf("expected %d matches for arg %s. All args: %s",
267*760c253cSXin Li			expectedCount, expectedRegex, cmd.Args)
268*760c253cSXin Li	}
269*760c253cSXin Li	return nil
270*760c253cSXin Li}
271*760c253cSXin Li
272*760c253cSXin Lifunc verifyArgOrder(cmd *command, expectedRegexes ...string) error {
273*760c253cSXin Li	compiledRegexes := []*regexp.Regexp{}
274*760c253cSXin Li	for _, regex := range expectedRegexes {
275*760c253cSXin Li		compiledRegexes = append(compiledRegexes, regexp.MustCompile(matchFullString(regex)))
276*760c253cSXin Li	}
277*760c253cSXin Li	expectedArgIndex := 0
278*760c253cSXin Li	for _, arg := range cmd.Args {
279*760c253cSXin Li		if expectedArgIndex == len(compiledRegexes) {
280*760c253cSXin Li			break
281*760c253cSXin Li		} else if compiledRegexes[expectedArgIndex].MatchString(arg) {
282*760c253cSXin Li			expectedArgIndex++
283*760c253cSXin Li		}
284*760c253cSXin Li	}
285*760c253cSXin Li	if expectedArgIndex != len(expectedRegexes) {
286*760c253cSXin Li		return fmt.Errorf("expected args %s in order. All args: %s",
287*760c253cSXin Li			expectedRegexes, cmd.Args)
288*760c253cSXin Li	}
289*760c253cSXin Li	return nil
290*760c253cSXin Li}
291*760c253cSXin Li
292*760c253cSXin Lifunc verifyEnvUpdate(cmd *command, expectedRegex string) error {
293*760c253cSXin Li	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
294*760c253cSXin Li	for _, update := range cmd.EnvUpdates {
295*760c253cSXin Li		if compiledRegex.MatchString(update) {
296*760c253cSXin Li			return nil
297*760c253cSXin Li		}
298*760c253cSXin Li	}
299*760c253cSXin Li	return fmt.Errorf("expected at least one match for env update %s. All env updates: %s",
300*760c253cSXin Li		expectedRegex, cmd.EnvUpdates)
301*760c253cSXin Li}
302*760c253cSXin Li
303*760c253cSXin Lifunc verifyNoEnvUpdate(cmd *command, expectedRegex string) error {
304*760c253cSXin Li	compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
305*760c253cSXin Li	updates := cmd.EnvUpdates
306*760c253cSXin Li	for _, update := range updates {
307*760c253cSXin Li		if compiledRegex.MatchString(update) {
308*760c253cSXin Li			return fmt.Errorf("expected no match for env update %s. All env updates: %s",
309*760c253cSXin Li				expectedRegex, cmd.EnvUpdates)
310*760c253cSXin Li		}
311*760c253cSXin Li	}
312*760c253cSXin Li	return nil
313*760c253cSXin Li}
314*760c253cSXin Li
315*760c253cSXin Lifunc hasInternalError(stderr string) bool {
316*760c253cSXin Li	return strings.Contains(stderr, "Internal error")
317*760c253cSXin Li}
318*760c253cSXin Li
319*760c253cSXin Lifunc verifyInternalError(stderr string) error {
320*760c253cSXin Li	if !hasInternalError(stderr) {
321*760c253cSXin Li		return fmt.Errorf("expected an internal error. Got: %s", stderr)
322*760c253cSXin Li	}
323*760c253cSXin Li	if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); !ok {
324*760c253cSXin Li		return fmt.Errorf("expected a source line reference. Got: %s", stderr)
325*760c253cSXin Li	}
326*760c253cSXin Li	return nil
327*760c253cSXin Li}
328*760c253cSXin Li
329*760c253cSXin Lifunc verifyNonInternalError(stderr string, expectedRegex string) error {
330*760c253cSXin Li	if hasInternalError(stderr) {
331*760c253cSXin Li		return fmt.Errorf("expected a non internal error. Got: %s", stderr)
332*760c253cSXin Li	}
333*760c253cSXin Li	if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); ok {
334*760c253cSXin Li		return fmt.Errorf("expected no source line reference. Got: %s", stderr)
335*760c253cSXin Li	}
336*760c253cSXin Li	if ok, _ := regexp.MatchString(matchFullString(expectedRegex), strings.TrimSpace(stderr)); !ok {
337*760c253cSXin Li		return fmt.Errorf("expected stderr matching %s. Got: %s", expectedRegex, stderr)
338*760c253cSXin Li	}
339*760c253cSXin Li	return nil
340*760c253cSXin Li}
341*760c253cSXin Li
342*760c253cSXin Lifunc matchFullString(regex string) string {
343*760c253cSXin Li	return "^" + regex + "$"
344*760c253cSXin Li}
345*760c253cSXin Li
346*760c253cSXin Lifunc newExitCodeError(exitCode int) error {
347*760c253cSXin Li	// It's actually hard to create an error that represents a command
348*760c253cSXin Li	// with exit code. Using a real command instead.
349*760c253cSXin Li	tmpCmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("exit %d", exitCode))
350*760c253cSXin Li	return tmpCmd.Run()
351*760c253cSXin Li}
352