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