• 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	"errors"
9	"fmt"
10	"io"
11	"path"
12	"strings"
13	"testing"
14)
15
16func TestClangTidyBasename(t *testing.T) {
17	withClangTidyTestContext(t, func(ctx *testContext) {
18		testData := []struct {
19			in  string
20			out string
21		}{
22			{"./x86_64-cros-linux-gnu-clang", ".*/clang-tidy"},
23			{"./x86_64-cros-linux-gnu-clang++", ".*/clang-tidy"},
24		}
25
26		var clangTidyCmd *command
27		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
28			if ctx.cmdCount == 2 {
29				clangTidyCmd = cmd
30			}
31			return nil
32		}
33
34		for _, tt := range testData {
35			ctx.cmdCount = 0
36			clangTidyCmd = nil
37			ctx.must(callCompiler(ctx, ctx.cfg,
38				ctx.newCommand(tt.in, mainCc)))
39			if ctx.cmdCount != 3 {
40				t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
41			}
42			if err := verifyPath(clangTidyCmd, tt.out); err != nil {
43				t.Error(err)
44			}
45		}
46	})
47}
48
49func TestClangTidyClangResourceDir(t *testing.T) {
50	withClangTidyTestContext(t, func(ctx *testContext) {
51		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
52			switch ctx.cmdCount {
53			case 1:
54				if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
55					t.Error(err)
56				}
57				if err := verifyArgOrder(cmd, "--print-resource-dir"); err != nil {
58					t.Error(err)
59				}
60				fmt.Fprint(stdout, "someResourcePath")
61				return nil
62			case 2:
63				if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil {
64					return err
65				}
66				if err := verifyArgOrder(cmd, "-resource-dir=someResourcePath", mainCc); err != nil {
67					return err
68				}
69				return nil
70			case 3:
71				if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
72					t.Error(err)
73				}
74				return nil
75			default:
76				t.Fatalf("unexpected command %#v", cmd)
77				return nil
78			}
79		}
80		ctx.must(callCompiler(ctx, ctx.cfg,
81			ctx.newCommand(clangX86_64, mainCc)))
82		if ctx.cmdCount != 3 {
83			t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
84		}
85	})
86}
87
88func TestClangTidyArgOrder(t *testing.T) {
89	withClangTidyTestContext(t, func(ctx *testContext) {
90		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
91			if ctx.cmdCount == 2 {
92				if err := verifyArgOrder(cmd, "-checks=.*", mainCc, "--", "-resource-dir=.*", mainCc, "--some_arg"); err != nil {
93					return err
94				}
95			}
96			return nil
97		}
98		ctx.must(callCompiler(ctx, ctx.cfg,
99			ctx.newCommand(clangX86_64, mainCc, "--some_arg")))
100		if ctx.cmdCount != 3 {
101			t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
102		}
103	})
104}
105
106func TestForwardStdOutAndStderrFromClangTidyCall(t *testing.T) {
107	withClangTidyTestContext(t, func(ctx *testContext) {
108		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
109			if ctx.cmdCount == 2 {
110				fmt.Fprint(stdout, "somemessage")
111				fmt.Fprint(stderr, "someerror")
112			}
113			return nil
114		}
115		ctx.must(callCompiler(ctx, ctx.cfg,
116			ctx.newCommand(clangX86_64, mainCc)))
117		if ctx.stdoutString() != "somemessage" {
118			t.Errorf("stdout was not forwarded. Got: %s", ctx.stdoutString())
119		}
120		if ctx.stderrString() != "someerror" {
121			t.Errorf("stderr was not forwarded. Got: %s", ctx.stderrString())
122		}
123	})
124}
125
126func TestIgnoreNonZeroExitCodeFromClangTidy(t *testing.T) {
127	withClangTidyTestContext(t, func(ctx *testContext) {
128		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
129			if ctx.cmdCount == 2 {
130				return newExitCodeError(23)
131			}
132			return nil
133		}
134		ctx.must(callCompiler(ctx, ctx.cfg,
135			ctx.newCommand(clangX86_64, mainCc)))
136		stderr := ctx.stderrString()
137		if err := verifyNonInternalError(stderr, "clang-tidy failed"); err != nil {
138			t.Error(err)
139		}
140	})
141}
142
143func TestReportGeneralErrorsFromClangTidy(t *testing.T) {
144	withClangTidyTestContext(t, func(ctx *testContext) {
145		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
146			if ctx.cmdCount == 2 {
147				return errors.New("someerror")
148			}
149			return nil
150		}
151		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg,
152			ctx.newCommand(clangX86_64, mainCc)))
153		if err := verifyInternalError(stderr); err != nil {
154			t.Fatal(err)
155		}
156		if !strings.Contains(stderr, "someerror") {
157			t.Errorf("unexpected error. Got: %s", stderr)
158		}
159	})
160}
161
162func TestOmitClangTidyForGcc(t *testing.T) {
163	withClangTidyTestContext(t, func(ctx *testContext) {
164		ctx.must(callCompiler(ctx, ctx.cfg,
165			ctx.newCommand(gccX86_64, mainCc)))
166		if ctx.cmdCount > 1 {
167			t.Errorf("expected 1 command. Got: %d", ctx.cmdCount)
168		}
169	})
170}
171
172func TestOmitClangTidyForGccWithClangSyntax(t *testing.T) {
173	withClangTidyTestContext(t, func(ctx *testContext) {
174		ctx.must(callCompiler(ctx, ctx.cfg,
175			ctx.newCommand(gccX86_64, "-clang-syntax", mainCc)))
176		if ctx.cmdCount > 2 {
177			t.Errorf("expected 2 commands. Got: %d", ctx.cmdCount)
178		}
179	})
180}
181
182func TestUseClangTidyBasedOnFileExtension(t *testing.T) {
183	withClangTidyTestContext(t, func(ctx *testContext) {
184		testData := []struct {
185			args      []string
186			clangTidy bool
187		}{
188			{[]string{"main.cc"}, true},
189			{[]string{"main.cc"}, true},
190			{[]string{"main.C"}, true},
191			{[]string{"main.cxx"}, true},
192			{[]string{"main.c++"}, true},
193			{[]string{"main.xy"}, false},
194			{[]string{"-o", "main.cc"}, false},
195			{[]string{}, false},
196		}
197		for _, tt := range testData {
198			ctx.cmdCount = 0
199			ctx.must(callCompiler(ctx, ctx.cfg,
200				ctx.newCommand(clangX86_64, tt.args...)))
201			if ctx.cmdCount > 1 && !tt.clangTidy {
202				t.Errorf("expected a call to clang tidy but got none for args %s", tt.args)
203			}
204			if ctx.cmdCount == 1 && tt.clangTidy {
205				t.Errorf("expected no call to clang tidy but got one for args %s", tt.args)
206			}
207		}
208	})
209}
210
211func TestOmitCCacheWithClangTidy(t *testing.T) {
212	withClangTidyTestContext(t, func(ctx *testContext) {
213		ctx.cfg.useCCache = true
214
215		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
216			switch ctx.cmdCount {
217			case 1:
218				if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
219					t.Error(err)
220				}
221				return nil
222			case 2:
223				if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil {
224					return err
225				}
226				return nil
227			default:
228				return nil
229			}
230		}
231		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
232			ctx.newCommand(clangX86_64, mainCc)))
233		if ctx.cmdCount != 3 {
234			t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
235		}
236		if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
237			t.Error(err)
238		}
239	})
240}
241
242func TestPartiallyOmitGomaWithClangTidy(t *testing.T) {
243	withClangTidyTestContext(t, func(ctx *testContext) {
244		gomaPath := path.Join(ctx.tempDir, "gomacc")
245		// Create a file so the gomacc path is valid.
246		ctx.writeFile(gomaPath, "")
247		ctx.env = append(ctx.env, "GOMACC_PATH="+gomaPath)
248
249		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
250			switch ctx.cmdCount {
251			case 1:
252				if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
253					t.Error(err)
254				}
255				return nil
256			case 2:
257				if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil {
258					return err
259				}
260				return nil
261			default:
262				return nil
263			}
264		}
265		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
266			ctx.newCommand(clangX86_64, mainCc)))
267		if ctx.cmdCount != 3 {
268			t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
269		}
270		if err := verifyPath(cmd, gomaPath); err != nil {
271			t.Error(err)
272		}
273	})
274}
275
276func TestTriciumClangTidyIsProperlyDetectedFromEnv(t *testing.T) {
277	withClangTidyTestContext(t, func(ctx *testContext) {
278		ctx.env = []string{"WITH_TIDY=tricium"}
279		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
280			switch ctx.cmdCount {
281			case 1:
282				if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
283					t.Error(err)
284				}
285				return nil
286			case 2:
287				if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil {
288					return err
289				}
290
291				hasFixesFile := false
292				for _, arg := range cmd.Args {
293					if path := strings.TrimPrefix(arg, "--export-fixes="); path != arg {
294						hasFixesFile = true
295						if !strings.HasPrefix(path, ctx.cfg.triciumNitsDir+"/") {
296							t.Errorf("fixes file was %q; expected it to be in %q", path, ctx.cfg.triciumNitsDir)
297						}
298						break
299					}
300				}
301
302				if !hasFixesFile {
303					t.Error("no fixes file was provided to a tricium invocation")
304				}
305
306				return nil
307			default:
308				return nil
309			}
310		}
311		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
312			ctx.newCommand(clangX86_64, mainCc)))
313		if ctx.cmdCount != 3 {
314			t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
315		}
316		if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
317			t.Error(err)
318		}
319	})
320}
321
322func TestTriciumClangTidySkipsProtobufFiles(t *testing.T) {
323	withClangTidyTestContext(t, func(ctx *testContext) {
324		ctx.env = []string{"WITH_TIDY=tricium"}
325		cmd := ctx.must(callCompiler(ctx, ctx.cfg,
326			ctx.newCommand(clangX86_64, mainCc+".pb.cc")))
327		if ctx.cmdCount != 1 {
328			t.Errorf("expected tricium clang-tidy to not execute on a protobuf file")
329		}
330		if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
331			t.Error(err)
332		}
333	})
334}
335
336func testClangTidyFiltersClangTidySpecificFlagsWithPresetEnv(t *testing.T, ctx *testContext) {
337	addedFlag := "--some_clang_tidy=flag"
338	ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
339		switch ctx.cmdCount {
340		case 1:
341			if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
342				t.Error(err)
343			} else if err := verifyArgCount(cmd, 0, addedFlag); err != nil {
344				t.Error(err)
345			}
346			return nil
347		case 2:
348			if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil {
349				t.Error(err)
350			} else if verifyArgCount(cmd, 1, addedFlag); err != nil {
351				t.Error(err)
352			}
353			return nil
354		default:
355			return nil
356		}
357	}
358	cmd := ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc, "-clang-tidy-flag="+addedFlag)))
359	if ctx.cmdCount != 3 {
360		t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
361	}
362	if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
363		t.Error(err)
364	}
365}
366
367func TestClangTidyFiltersClangTidySpecificFlagsForTricium(t *testing.T) {
368	withClangTidyTestContext(t, func(ctx *testContext) {
369		ctx.env = []string{"WITH_TIDY=tricium"}
370		testClangTidyFiltersClangTidySpecificFlagsWithPresetEnv(t, ctx)
371	})
372}
373
374func TestClangTidyFiltersClangTidySpecificFlags(t *testing.T) {
375	withClangTidyTestContext(t, func(ctx *testContext) {
376		testClangTidyFiltersClangTidySpecificFlagsWithPresetEnv(t, ctx)
377	})
378}
379
380func TestClangTidyFlagsAreFilteredFromGccInvocations(t *testing.T) {
381	withTestContext(t, func(ctx *testContext) {
382		cmd := ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc, "-clang-tidy-flag=--foo")))
383		if err := verifyArgCount(cmd, 0, ".*--foo.*"); err != nil {
384			t.Error(err)
385		}
386	})
387}
388
389func TestTriciumReportsClangTidyCrashesGracefully(t *testing.T) {
390	withClangTidyTestContext(t, func(ctx *testContext) {
391		ctx.env = []string{"WITH_TIDY=tricium"}
392		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
393			switch ctx.cmdCount {
394			case 1:
395				if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
396					t.Error(err)
397				}
398				return nil
399			case 2:
400				if err := verifyPath(cmd, "usr/bin/clang-tidy"); err != nil {
401					return err
402				}
403
404				if _, err := io.WriteString(stdout, clangTidyCrashSubstring); err != nil {
405					return err
406				}
407				return nil
408			case 3:
409				if err := verifyPath(cmd, "usr/bin/clang"); err != nil {
410					t.Error(err)
411				}
412
413				args := cmd.Args
414				if len(args) < 3 {
415					t.Errorf("insufficient number of args provided; got %d; want at least 3", len(args))
416					return nil
417				}
418
419				lastArgs := args[len(args)-3:]
420				eArg, oArg, outFileArg := lastArgs[0], lastArgs[1], lastArgs[2]
421				if eArg != "-E" {
422					t.Errorf("got eArg=%q; wanted -E", eArg)
423				}
424
425				if oArg != "-o" {
426					t.Errorf("got oArg=%q; wanted -o", oArg)
427				}
428
429				wantPrefix := path.Join(ctx.cfg.crashArtifactsDir, "clang-tidy")
430				if !strings.HasPrefix(outFileArg, wantPrefix) {
431					t.Errorf("got out file %q; wanted one starting with %q", outFileArg, wantPrefix)
432				}
433
434				return nil
435			default:
436				return nil
437			}
438		}
439		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
440		if ctx.cmdCount != 4 {
441			t.Errorf("expected 3 calls. Got: %d", ctx.cmdCount)
442		}
443	})
444}
445
446func withClangTidyTestContext(t *testing.T, work func(ctx *testContext)) {
447	withTestContext(t, func(ctx *testContext) {
448		ctx.env = []string{"WITH_TIDY=1"}
449		work(ctx)
450	})
451}
452