xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/compiler_wrapper_test.go (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1// Copyright 2019 The ChromiumOS Authors
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	"errors"
10	"fmt"
11	"io"
12	"os"
13	"path"
14	"path/filepath"
15	"strings"
16	"syscall"
17	"testing"
18)
19
20func TestAddCommonFlags(t *testing.T) {
21	withTestContext(t, func(ctx *testContext) {
22		ctx.cfg.commonFlags = []string{"-someflag"}
23		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
24			ctx.newCommand(gccX86_64, mainCc)))
25		if err := verifyArgOrder(cmd, "-someflag", mainCc); err != nil {
26			t.Error(err)
27		}
28	})
29}
30
31func TestAddGccConfigFlags(t *testing.T) {
32	withTestContext(t, func(ctx *testContext) {
33		ctx.cfg.gccFlags = []string{"-someflag"}
34		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
35			ctx.newCommand(gccX86_64, mainCc)))
36		if err := verifyArgOrder(cmd, "-someflag", mainCc); err != nil {
37			t.Error(err)
38		}
39	})
40}
41
42func TestAddClangConfigFlags(t *testing.T) {
43	withTestContext(t, func(ctx *testContext) {
44		ctx.cfg.clangFlags = []string{"-someflag"}
45		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
46			ctx.newCommand(clangX86_64, mainCc)))
47		if err := verifyArgOrder(cmd, "-someflag", mainCc); err != nil {
48			t.Error(err)
49		}
50	})
51}
52
53func TestLogGeneralExecError(t *testing.T) {
54	withTestContext(t, func(ctx *testContext) {
55		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
56			return errors.New("someerror")
57		}
58		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)))
59		if err := verifyInternalError(stderr); err != nil {
60			t.Fatal(err)
61		}
62		if !strings.Contains(stderr, gccX86_64) {
63			t.Errorf("could not find compiler path on stderr. Got: %s", stderr)
64		}
65		if !strings.Contains(stderr, "someerror") {
66			t.Errorf("could not find original error on stderr. Got: %s", stderr)
67		}
68	})
69}
70
71func TestForwardStdin(t *testing.T) {
72	withTestContext(t, func(ctx *testContext) {
73		io.WriteString(&ctx.stdinBuffer, "someinput")
74		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
75			stdinStr := ctx.readAllString(stdin)
76			if stdinStr != "someinput" {
77				return fmt.Errorf("unexpected stdin. Got: %s", stdinStr)
78			}
79			return nil
80		}
81		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, "-", mainCc)))
82	})
83}
84
85func TestLogMissingCCacheExecError(t *testing.T) {
86	withTestContext(t, func(ctx *testContext) {
87		ctx.cfg.useCCache = true
88
89		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
90			return syscall.ENOENT
91		}
92		ctx.stderrBuffer.Reset()
93		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)))
94		if err := verifyNonInternalError(stderr, "ccache not found under .*. Please install it"); err != nil {
95			t.Fatal(err)
96		}
97	})
98}
99
100func TestGomaDisablesRusage(t *testing.T) {
101	withTestContext(t, func(ctx *testContext) {
102		gomaPath := path.Join(ctx.tempDir, "gomacc")
103		ctx.writeFile(gomaPath, "")
104		ctx.env = []string{"GOMACC_PATH=" + gomaPath}
105		logFileName := filepath.Join(ctx.tempDir, "rusage.log")
106		ctx.env = []string{
107			"TOOLCHAIN_RUSAGE_OUTPUT=" + logFileName,
108			"GOMACC_PATH=" + gomaPath,
109		}
110		cmd := ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)))
111		// Ensure Goma was used
112		if err := verifyPath(cmd, gomaPath); err != nil {
113			t.Fatal(err)
114		}
115		if err := verifyArgOrder(cmd, gccX86_64+".real", mainCc); err != nil {
116			t.Error(err)
117		}
118		// Ensure rusage log was not created
119		if _, err := os.Stat(logFileName); err == nil {
120			t.Errorf("Logfile shouldn't have been created at TOOLCHAIN_RUSAGE_OUTPUT path %q but was", logFileName)
121		} else if !os.IsNotExist(err) {
122			t.Fatalf("error checking for rusage logfile at %q: %v", logFileName, err)
123		}
124	})
125}
126
127func TestLogRusageAndForceDisableWError(t *testing.T) {
128	withTestContext(t, func(ctx *testContext) {
129		ctx.NoteTestWritesToUmask()
130
131		logFileName := filepath.Join(ctx.tempDir, "rusage.log")
132		ctx.env = []string{
133			"FORCE_DISABLE_WERROR=1",
134			"TOOLCHAIN_RUSAGE_OUTPUT=" + logFileName,
135		}
136		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
137			switch ctx.cmdCount {
138			case 1:
139				io.WriteString(stderr, arbitraryWerrorStderr)
140				return newExitCodeError(1)
141			case 2:
142				return nil
143			default:
144				t.Fatalf("unexpected command: %#v", cmd)
145				return nil
146			}
147		}
148		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
149		if _, err := os.Stat(logFileName); os.IsNotExist(err) {
150			t.Errorf("no logfile created at TOOLCHAIN_RUSAGE_OUTPUT path %q", logFileName)
151		} else if err != nil {
152			t.Fatalf("error checking for rusage logfile at %q: %v", logFileName, err)
153		}
154		if ctx.cmdCount != 2 {
155			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
156		}
157	})
158}
159
160func TestErrorOnLogRusageAndBisect(t *testing.T) {
161	withTestContext(t, func(ctx *testContext) {
162		ctx.NoteTestWritesToUmask()
163
164		ctx.env = []string{
165			"BISECT_STAGE=xyz",
166			"TOOLCHAIN_RUSAGE_OUTPUT=rusage.log",
167		}
168		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)))
169		if err := verifyNonInternalError(stderr, "TOOLCHAIN_RUSAGE_OUTPUT is meaningless with BISECT_STAGE"); err != nil {
170			t.Error(err)
171		}
172	})
173}
174
175func TestErrorOnBisectAndForceDisableWError(t *testing.T) {
176	withTestContext(t, func(ctx *testContext) {
177		ctx.NoteTestWritesToUmask()
178
179		ctx.env = []string{
180			"BISECT_STAGE=xyz",
181			"FORCE_DISABLE_WERROR=1",
182		}
183		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
184		if err := verifyNonInternalError(stderr, "BISECT_STAGE is meaningless with FORCE_DISABLE_WERROR"); err != nil {
185			t.Error(err)
186		}
187	})
188}
189
190func TestPrintUserCompilerError(t *testing.T) {
191	buffer := bytes.Buffer{}
192	printCompilerError(&buffer, newUserErrorf("abcd"))
193	if buffer.String() != "abcd\n" {
194		t.Errorf("Unexpected string. Got: %s", buffer.String())
195	}
196}
197
198func TestPrintOtherCompilerError(t *testing.T) {
199	buffer := bytes.Buffer{}
200	printCompilerError(&buffer, errors.New("abcd"))
201	if buffer.String() != "Internal error. Please report to [email protected].\nabcd\n" {
202		t.Errorf("Unexpected string. Got: %s", buffer.String())
203	}
204}
205
206func TestPrintOtherCompilerErrorForAndroidLLVM(t *testing.T) {
207	buffer := bytes.Buffer{}
208
209	oldConfigName := ConfigName
210	defer func() { ConfigName = oldConfigName }()
211
212	ConfigName = "android"
213	printCompilerError(&buffer, errors.New("abcd"))
214	if buffer.String() != "Internal error. Please report to [email protected].\nabcd\n" {
215		t.Errorf("Unexpected string. Got: %s", buffer.String())
216	}
217}
218
219func TestCalculateAndroidWrapperPath(t *testing.T) {
220	t.Parallel()
221
222	testCases := []struct {
223		mainBuilderPath string
224		absWrapperPath  string
225		want            string
226	}{
227		{
228			mainBuilderPath: "/foo/bar",
229			absWrapperPath:  "/bar/baz",
230			want:            "/foo/baz.real",
231		},
232		{
233			mainBuilderPath: "/my_wrapper",
234			absWrapperPath:  "/bar/baz",
235			want:            "/baz.real",
236		},
237		{
238			mainBuilderPath: "no_seps",
239			absWrapperPath:  "/bar/baz",
240			want:            "baz.real",
241		},
242		{
243			mainBuilderPath: "./a_sep",
244			absWrapperPath:  "/bar/baz",
245			want:            "./baz.real",
246		},
247	}
248
249	for _, tc := range testCases {
250		if result := calculateAndroidWrapperPath(tc.mainBuilderPath, tc.absWrapperPath); result != tc.want {
251			t.Errorf("Failed calculating the wrapper path with (%q, %q); got %q, want %q", tc.mainBuilderPath, tc.absWrapperPath, result, tc.want)
252		}
253	}
254}
255
256// If "crash-diagnostics-dir" flag is not provided, add one in
257func TestCrashDiagDefault(t *testing.T) {
258	withTestContext(t, func(ctx *testContext) {
259		ctx.env = []string{
260			"CROS_ARTIFACTS_TMP_DIR=/tmp/foo",
261		}
262		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
263			ctx.newCommand(clangX86_64, mainCc)))
264
265		// Verify that we added the default flag
266		if err := verifyArgCount(cmd, 1, "-fcrash-diagnostics-dir=/tmp/foo/toolchain/clang_crash_diagnostics"); err != nil {
267			t.Error(err)
268		}
269	})
270}
271
272// If "crash-diagnostics-dir" flag is already provided by the user, only use that flag and don't add a dupe
273func TestCrashDiagUserFlag(t *testing.T) {
274	withTestContext(t, func(ctx *testContext) {
275		ctx.env = []string{
276			"CROS_ARTIFACTS_TMP_DIR=/tmp/foo",
277		}
278		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
279			ctx.newCommand(clangX86_64, mainCc, "-fcrash-diagnostics-dir=/build/something/foozz")))
280
281		// Verify that user flag is not removed
282		if err := verifyArgCount(cmd, 1, "-fcrash-diagnostics-dir=/build/something/foozz"); err != nil {
283			t.Error(err)
284		}
285
286		// Verify that we did not add the default flag
287		if err := verifyArgCount(cmd, 0, "-fcrash-diagnostics-dir=/tmp/foo/toolchain/clang_crash_diagnostics"); err != nil {
288			t.Error(err)
289		}
290	})
291}
292