xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/compile_with_fallback_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	"errors"
9	"fmt"
10	"io"
11	"io/ioutil"
12	"os"
13	"path/filepath"
14	"strings"
15	"syscall"
16	"testing"
17)
18
19// Save this off before goroutines start running, since this necessarily involves modifying the
20// value for our umask, and that screams subtle race conditions. :)
21var umaskAtStartup = func() os.FileMode {
22	umask := syscall.Umask(0)
23	syscall.Umask(umask)
24	return os.FileMode(umask)
25}()
26
27func TestOmitFallbackCompileForSuccessfulCall(t *testing.T) {
28	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
29		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
30		if ctx.cmdCount != 1 {
31			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
32		}
33	})
34}
35
36func TestOmitFallbackCompileForGeneralError(t *testing.T) {
37	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
38		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
39			return errors.New("someerror")
40		}
41		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
42		if err := verifyInternalError(stderr); err != nil {
43			t.Fatal(err)
44		}
45		if !strings.Contains(stderr, "someerror") {
46			t.Errorf("unexpected error. Got: %s", stderr)
47		}
48		if ctx.cmdCount != 1 {
49			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
50		}
51	})
52}
53
54func TestCompileWithFallbackForNonZeroExitCode(t *testing.T) {
55	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
56		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
57			switch ctx.cmdCount {
58			case 1:
59				return newExitCodeError(1)
60			case 2:
61				if err := verifyPath(cmd, "fallback_compiler/clang"); err != nil {
62					return err
63				}
64				if err := verifyEnvUpdate(cmd, "ANDROID_LLVM_PREBUILT_COMPILER_PATH="); err != nil {
65					return err
66				}
67				return nil
68			default:
69				t.Fatalf("unexpected command: %#v", cmd)
70				return nil
71			}
72		}
73		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
74		if ctx.cmdCount != 2 {
75			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
76		}
77	})
78}
79
80func TestCompileWithFallbackForwardStdoutAndStderr(t *testing.T) {
81	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
82		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
83			switch ctx.cmdCount {
84			case 1:
85				fmt.Fprint(stdout, "originalmessage")
86				fmt.Fprint(stderr, "originalerror")
87				return newExitCodeError(1)
88			case 2:
89				fmt.Fprint(stdout, "fallbackmessage")
90				fmt.Fprint(stderr, "fallbackerror")
91				return nil
92			default:
93				t.Fatalf("unexpected command: %#v", cmd)
94				return nil
95			}
96		}
97		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
98		if err := verifyNonInternalError(ctx.stderrString(), "originalerrorfallbackerror"); err != nil {
99			t.Error(err)
100		}
101		if !strings.Contains(ctx.stdoutString(), "originalmessagefallbackmessage") {
102			t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString())
103		}
104	})
105}
106
107func TestForwardGeneralErrorWhenFallbackCompileFails(t *testing.T) {
108	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
109		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
110			switch ctx.cmdCount {
111			case 1:
112				return newExitCodeError(1)
113			case 2:
114				return errors.New("someerror")
115			default:
116				t.Fatalf("unexpected command: %#v", cmd)
117				return nil
118			}
119		}
120		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
121		if err := verifyInternalError(stderr); err != nil {
122			t.Error(err)
123		}
124		if !strings.Contains(stderr, "someerror") {
125			t.Errorf("unexpected stderr. Got: %s", stderr)
126		}
127	})
128}
129
130func TestForwardExitCodeWhenFallbackCompileFails(t *testing.T) {
131	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
132		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
133			switch ctx.cmdCount {
134			case 1:
135				return newExitCodeError(1)
136			case 2:
137				return newExitCodeError(2)
138			default:
139				t.Fatalf("unexpected command: %#v", cmd)
140				return nil
141			}
142		}
143		exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))
144		if exitCode != 2 {
145			t.Errorf("unexpected exit code. Got: %d", exitCode)
146		}
147	})
148}
149
150func TestForwardStdinToFallbackCompile(t *testing.T) {
151	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
152		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
153			stdinStr := ctx.readAllString(stdin)
154			if stdinStr != "someinput" {
155				return fmt.Errorf("unexpected stdin. Got: %s", stdinStr)
156			}
157
158			switch ctx.cmdCount {
159			case 1:
160				return newExitCodeError(1)
161			case 2:
162				return nil
163			default:
164				t.Fatalf("unexpected command: %#v", cmd)
165				return nil
166			}
167		}
168		io.WriteString(&ctx.stdinBuffer, "someinput")
169		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, "-", mainCc)))
170	})
171}
172
173func TestCompileWithFallbackExtraArgs(t *testing.T) {
174	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
175		testData := []struct {
176			compiler        string
177			expectExtraArgs bool
178		}{
179			{"./clang", true},
180			{"./clang++", true},
181			{"./clang-tidy", false},
182		}
183		ctx.env = append(ctx.env, "ANDROID_LLVM_FALLBACK_DISABLED_WARNINGS=-a -b")
184		extraArgs := []string{"-fno-color-diagnostics", "-a", "-b"}
185		for _, tt := range testData {
186			ctx.cmdCount = 0
187			ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
188				switch ctx.cmdCount {
189				case 1:
190					if tt.expectExtraArgs {
191						if err := verifyArgOrder(cmd, extraArgs...); err != nil {
192							return err
193						}
194					} else {
195						for _, arg := range extraArgs {
196							if err := verifyArgCount(cmd, 0, arg); err != nil {
197								return err
198							}
199						}
200					}
201					return newExitCodeError(1)
202				case 2:
203					for _, arg := range extraArgs {
204						if err := verifyArgCount(cmd, 0, arg); err != nil {
205							return err
206						}
207					}
208					return nil
209				default:
210					t.Fatalf("unexpected command: %#v", cmd)
211					return nil
212				}
213			}
214			ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(tt.compiler, mainCc)))
215			if ctx.cmdCount != 2 {
216				t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
217			}
218		}
219	})
220}
221
222func TestCompileWithFallbackLogCommandAndErrors(t *testing.T) {
223	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
224		ctx.NoteTestReadsFromUmask()
225
226		ctx.env = append(ctx.env, "ANDROID_LLVM_FALLBACK_DISABLED_WARNINGS=-a -b")
227		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
228			switch ctx.cmdCount {
229			case 1:
230				fmt.Fprint(stderr, "someerror\n")
231				return newExitCodeError(1)
232			case 2:
233				return nil
234			default:
235				t.Fatalf("unexpected command: %#v", cmd)
236				return nil
237			}
238		}
239		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
240
241		log := readCompileWithFallbackErrorLog(ctx)
242		if log != `==================COMMAND:====================
243./clang.real main.cc -fno-color-diagnostics -a -b
244
245someerror
246==============================================
247
248` {
249			t.Errorf("unexpected log. Got: %s", log)
250		}
251
252		entry, _ := os.Lstat(filepath.Join(ctx.tempDir, "fallback_stderr"))
253		if entry.Mode()&0777 != 0644 & ^umaskAtStartup {
254			t.Errorf("unexpected mode for logfile. Got: %#o", entry.Mode())
255		}
256	})
257}
258
259func TestCompileWithFallbackAppendToLog(t *testing.T) {
260	withCompileWithFallbackTestContext(t, func(ctx *testContext) {
261		ctx.writeFile(filepath.Join(ctx.tempDir, "fallback_stderr"), "oldContent\n")
262		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
263			switch ctx.cmdCount {
264			case 1:
265				return newExitCodeError(1)
266			case 2:
267				return nil
268			default:
269				t.Fatalf("unexpected command: %#v", cmd)
270				return nil
271			}
272		}
273		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
274
275		log := readCompileWithFallbackErrorLog(ctx)
276		if !strings.Contains(log, "oldContent") {
277			t.Errorf("old content not present: %s", log)
278		}
279		if !strings.Contains(log, "clang.real") {
280			t.Errorf("new content not present: %s", log)
281		}
282	})
283}
284
285func withCompileWithFallbackTestContext(t *testing.T, work func(ctx *testContext)) {
286	withTestContext(t, func(ctx *testContext) {
287		ctx.cfg.isAndroidWrapper = true
288		ctx.env = []string{
289			"ANDROID_LLVM_PREBUILT_COMPILER_PATH=fallback_compiler",
290			"ANDROID_LLVM_STDERR_REDIRECT=" + filepath.Join(ctx.tempDir, "fallback_stderr"),
291		}
292		work(ctx)
293	})
294}
295
296func readCompileWithFallbackErrorLog(ctx *testContext) string {
297	logFile := filepath.Join(ctx.tempDir, "fallback_stderr")
298	data, err := ioutil.ReadFile(logFile)
299	if err != nil {
300		ctx.t.Fatalf("error reading log file %s: %s", logFile, err)
301	}
302	return string(data)
303}
304