xref: /aosp_15_r20/external/toolchain-utils/compiler_wrapper/crash_builds_test.go (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1// Copyright 2022 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	"io"
10	"strings"
11	"testing"
12)
13
14func TestBuildWithAutoCrashDoesNothingIfCrashIsNotRequested(t *testing.T) {
15	withTestContext(t, func(ctx *testContext) {
16		neverAutoCrash := buildWithAutocrashPredicates{
17			allowInConfigure: true,
18			shouldAutocrash: func(env, *config, *command, compilerExecInfo) bool {
19				return false
20			},
21		}
22
23		exitCode, err := buildWithAutocrashImpl(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc), neverAutoCrash)
24		if err != nil {
25			t.Fatalf("unexpectedly failed with %v", err)
26		}
27		ctx.must(exitCode)
28		if ctx.cmdCount != 1 {
29			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
30		}
31	})
32}
33
34func TestBuildWithAutoCrashSkipsAutocrashLogicIfInConfigureAndConfigureChecksDisabled(t *testing.T) {
35	withTestContext(t, func(ctx *testContext) {
36		alwaysAutocrash := buildWithAutocrashPredicates{
37			allowInConfigure: false,
38			shouldAutocrash: func(env, *config, *command, compilerExecInfo) bool {
39				return true
40			},
41		}
42
43		ctx.env = append(ctx.env, "EBUILD_PHASE=configure")
44		exitCode, err := buildWithAutocrashImpl(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc), alwaysAutocrash)
45		if err != nil {
46			t.Fatalf("unexpectedly failed with %v", err)
47		}
48		ctx.must(exitCode)
49		if ctx.cmdCount != 1 {
50			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
51		}
52	})
53}
54
55func TestBuildWithAutoCrashRerunsIfPredicateRequestsCrash(t *testing.T) {
56	withTestContext(t, func(ctx *testContext) {
57		autocrashPostCmd := buildWithAutocrashPredicates{
58			allowInConfigure: true,
59			shouldAutocrash: func(env, *config, *command, compilerExecInfo) bool {
60				return true
61			},
62		}
63
64		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
65			hasDash := false
66			for _, arg := range cmd.Args {
67				if arg == "-" {
68					hasDash = true
69					break
70				}
71			}
72
73			switch ctx.cmdCount {
74			case 1:
75				if hasDash {
76					t.Error("Got `-` on command 1; didn't want that.")
77				}
78				return nil
79			case 2:
80				if !hasDash {
81					t.Error("Didn't get `-` on command 2; wanted that.")
82				} else {
83					input := stdin.(*bytes.Buffer)
84					if s := input.String(); !strings.Contains(s, autocrashProgramLine) {
85						t.Errorf("Input was %q; expected %q to be in it", s, autocrashProgramLine)
86					}
87				}
88				return nil
89			default:
90				t.Fatalf("Unexpected command count: %d", ctx.cmdCount)
91				panic("Unreachable")
92			}
93		}
94
95		exitCode, err := buildWithAutocrashImpl(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc), autocrashPostCmd)
96		if err != nil {
97			t.Fatalf("unexpectedly failed with %v", err)
98		}
99		ctx.must(exitCode)
100
101		if ctx.cmdCount != 2 {
102			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
103		}
104	})
105}
106
107func TestBuildWithAutoCrashAddsDashAndWritesToStdinIfInputFileIsNotStdin(t *testing.T) {
108	withTestContext(t, func(ctx *testContext) {
109		autocrashPostCmd := buildWithAutocrashPredicates{
110			allowInConfigure: true,
111			shouldAutocrash: func(env, *config, *command, compilerExecInfo) bool {
112				return true
113			},
114		}
115
116		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
117			numDashes := 0
118			for _, arg := range cmd.Args {
119				if arg == "-" {
120					numDashes++
121				}
122			}
123
124			switch ctx.cmdCount {
125			case 1:
126				if numDashes != 0 {
127					t.Errorf("Got %d dashes on command 1; want 0", numDashes)
128				}
129				return nil
130			case 2:
131				if numDashes != 1 {
132					t.Errorf("Got %d dashes on command 2; want 1", numDashes)
133				}
134
135				input := stdin.(*bytes.Buffer).String()
136				stdinHasAutocrashLine := strings.Contains(input, autocrashProgramLine)
137				if !stdinHasAutocrashLine {
138					t.Error("Got no autocrash line on the second command; wanted that")
139				}
140				return nil
141			default:
142				t.Fatalf("Unexpected command count: %d", ctx.cmdCount)
143				panic("Unreachable")
144			}
145		}
146
147		exitCode, err := buildWithAutocrashImpl(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc), autocrashPostCmd)
148		if err != nil {
149			t.Fatalf("unexpectedly failed with %v", err)
150		}
151		ctx.must(exitCode)
152
153		if ctx.cmdCount != 2 {
154			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
155		}
156	})
157}
158
159func TestBuildWithAutoCrashAppendsToStdinIfStdinIsTheOnlyInputFile(t *testing.T) {
160	withTestContext(t, func(ctx *testContext) {
161		autocrashPostCmd := buildWithAutocrashPredicates{
162			allowInConfigure: true,
163			shouldAutocrash: func(env, *config, *command, compilerExecInfo) bool {
164				return true
165			},
166		}
167
168		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
169			numDashes := 0
170			for _, arg := range cmd.Args {
171				if arg == "-" {
172					numDashes++
173				}
174			}
175
176			if numDashes != 1 {
177				t.Errorf("Got %d dashes on command %d (args: %#v); want 1", numDashes, ctx.cmdCount, cmd.Args)
178			}
179
180			input := stdin.(*bytes.Buffer).String()
181			stdinHasAutocrashLine := strings.Contains(input, autocrashProgramLine)
182
183			switch ctx.cmdCount {
184			case 1:
185				if stdinHasAutocrashLine {
186					t.Error("Got autocrash line on the first command; did not want that")
187				}
188				return nil
189			case 2:
190				if !stdinHasAutocrashLine {
191					t.Error("Got no autocrash line on the second command; wanted that")
192				}
193				return nil
194			default:
195				t.Fatalf("Unexpected command count: %d", ctx.cmdCount)
196				panic("Unreachable")
197			}
198		}
199
200		exitCode, err := buildWithAutocrashImpl(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "-x", "c", "-"), autocrashPostCmd)
201		if err != nil {
202			t.Fatalf("unexpectedly failed with %v", err)
203		}
204		ctx.must(exitCode)
205
206		if ctx.cmdCount != 2 {
207			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
208		}
209	})
210}
211
212func TestCrashBuildFiltersObjectFileOptionOnCrashes(t *testing.T) {
213	withTestContext(t, func(ctx *testContext) {
214		autocrashPostCmd := buildWithAutocrashPredicates{
215			allowInConfigure: true,
216			shouldAutocrash: func(env, *config, *command, compilerExecInfo) bool {
217				return true
218			},
219		}
220
221		const outputFileName = "/path/to/foo.o"
222
223		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
224			cmdOutputArg := (*string)(nil)
225			for i, e := range cmd.Args {
226				if e == "-o" {
227					// Assume something follows. If not, we'll crash and the
228					// test will fail.
229					cmdOutputArg = &cmd.Args[i+1]
230				}
231			}
232
233			switch ctx.cmdCount {
234			case 1:
235				if cmdOutputArg == nil || *cmdOutputArg != outputFileName {
236					t.Errorf("Got command args %q; want `-o %q` in them", cmd.Args, outputFileName)
237				}
238				return nil
239			case 2:
240				if cmdOutputArg != nil {
241					t.Errorf("Got command args %q; want no mention of `-o %q` in them", cmd.Args, outputFileName)
242				}
243				return nil
244			default:
245				t.Fatalf("Unexpected command count: %d", ctx.cmdCount)
246				panic("Unreachable")
247			}
248		}
249
250		exitCode, err := buildWithAutocrashImpl(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "-o", outputFileName, mainCc), autocrashPostCmd)
251		if err != nil {
252			t.Fatalf("unexpectedly failed with %v", err)
253		}
254		ctx.must(exitCode)
255
256		if ctx.cmdCount != 2 {
257			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
258		}
259	})
260}
261