• 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	"encoding/json"
9	"errors"
10	"fmt"
11	"io"
12	"io/ioutil"
13	"os"
14	"path/filepath"
15	"strings"
16	"testing"
17)
18
19func TestOmitDoubleBuildForSuccessfulCall(t *testing.T) {
20	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
21		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
22		if ctx.cmdCount != 1 {
23			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
24		}
25	})
26}
27
28func TestOmitDoubleBuildForGeneralError(t *testing.T) {
29	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
30		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
31			return errors.New("someerror")
32		}
33		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
34		if err := verifyInternalError(stderr); err != nil {
35			t.Fatal(err)
36		}
37		if !strings.Contains(stderr, "someerror") {
38			t.Errorf("unexpected error. Got: %s", stderr)
39		}
40		if ctx.cmdCount != 1 {
41			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
42		}
43	})
44}
45
46func TestDoubleBuildWithWNoErrorFlag(t *testing.T) {
47	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
48		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
49			switch ctx.cmdCount {
50			case 1:
51				if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil {
52					return err
53				}
54				fmt.Fprint(stderr, "-Werror originalerror")
55				return newExitCodeError(1)
56			case 2:
57				if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil {
58					return err
59				}
60				return nil
61			default:
62				t.Fatalf("unexpected command: %#v", cmd)
63				return nil
64			}
65		}
66		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
67		if ctx.cmdCount != 2 {
68			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
69		}
70	})
71}
72
73func TestKnownConfigureFileParsing(t *testing.T) {
74	withTestContext(t, func(ctx *testContext) {
75		for _, f := range []string{"conftest.c", "conftest.cpp", "/dev/null"} {
76			if !isLikelyAConfTest(ctx.cfg, ctx.newCommand(clangX86_64, f)) {
77				t.Errorf("%q isn't considered a conf test file", f)
78			}
79		}
80	})
81}
82
83func TestDoubleBuildWithKnownConfigureFile(t *testing.T) {
84	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
85		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
86			switch ctx.cmdCount {
87			case 1:
88				if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil {
89					return err
90				}
91				fmt.Fprint(stderr, "-Werror originalerror")
92				return newExitCodeError(1)
93			default:
94				t.Fatalf("unexpected command: %#v", cmd)
95				return nil
96			}
97		}
98
99		ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "conftest.c")))
100		if ctx.cmdCount != 1 {
101			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
102		}
103	})
104}
105
106func TestDoubleBuildWithWNoErrorAndCCache(t *testing.T) {
107	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
108		ctx.cfg.useCCache = true
109		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
110			switch ctx.cmdCount {
111			case 1:
112				// TODO: This is a bug in the old wrapper that it drops the ccache path
113				// during double build. Fix this once we don't compare to the old wrapper anymore.
114				if err := verifyPath(cmd, "ccache"); err != nil {
115					return err
116				}
117				fmt.Fprint(stderr, "-Werror originalerror")
118				return newExitCodeError(1)
119			case 2:
120				if err := verifyPath(cmd, "ccache"); err != nil {
121					return err
122				}
123				return nil
124			default:
125				t.Fatalf("unexpected command: %#v", cmd)
126				return nil
127			}
128		}
129		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
130		if ctx.cmdCount != 2 {
131			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
132		}
133	})
134}
135
136func TestForwardStdoutAndStderrWhenDoubleBuildSucceeds(t *testing.T) {
137	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
138		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
139			switch ctx.cmdCount {
140			case 1:
141				fmt.Fprint(stdout, "originalmessage")
142				fmt.Fprint(stderr, "-Werror originalerror")
143				return newExitCodeError(1)
144			case 2:
145				fmt.Fprint(stdout, "retrymessage")
146				fmt.Fprint(stderr, "retryerror")
147				return nil
148			default:
149				t.Fatalf("unexpected command: %#v", cmd)
150				return nil
151			}
152		}
153		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
154		if err := verifyNonInternalError(ctx.stderrString(), "retryerror"); err != nil {
155			t.Error(err)
156		}
157		if !strings.Contains(ctx.stdoutString(), "retrymessage") {
158			t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString())
159		}
160	})
161}
162
163func TestForwardStdoutAndStderrWhenDoubleBuildFails(t *testing.T) {
164	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
165		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
166			switch ctx.cmdCount {
167			case 1:
168				fmt.Fprint(stdout, "originalmessage")
169				fmt.Fprint(stderr, "-Werror originalerror")
170				return newExitCodeError(3)
171			case 2:
172				fmt.Fprint(stdout, "retrymessage")
173				fmt.Fprint(stderr, "retryerror")
174				return newExitCodeError(5)
175			default:
176				t.Fatalf("unexpected command: %#v", cmd)
177				return nil
178			}
179		}
180		exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))
181		if exitCode != 3 {
182			t.Errorf("unexpected exitcode. Got: %d", exitCode)
183		}
184		if err := verifyNonInternalError(ctx.stderrString(), "-Werror originalerror"); err != nil {
185			t.Error(err)
186		}
187		if !strings.Contains(ctx.stdoutString(), "originalmessage") {
188			t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString())
189		}
190	})
191}
192
193func TestForwardStdinFromDoubleBuild(t *testing.T) {
194	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
195		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
196			// Note: This is called for the clang syntax call as well as for
197			// the gcc call, and we assert that stdin is cloned and forwarded
198			// to both.
199			stdinStr := ctx.readAllString(stdin)
200			if stdinStr != "someinput" {
201				return fmt.Errorf("unexpected stdin. Got: %s", stdinStr)
202			}
203
204			switch ctx.cmdCount {
205			case 1:
206				fmt.Fprint(stderr, "-Werror originalerror")
207				return newExitCodeError(1)
208			case 2:
209				return nil
210			default:
211				t.Fatalf("unexpected command: %#v", cmd)
212				return nil
213			}
214		}
215		io.WriteString(&ctx.stdinBuffer, "someinput")
216		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "-", mainCc)))
217	})
218}
219
220func TestForwardGeneralErrorWhenDoubleBuildFails(t *testing.T) {
221	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
222		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
223			switch ctx.cmdCount {
224			case 1:
225				fmt.Fprint(stderr, "-Werror originalerror")
226				return newExitCodeError(3)
227			case 2:
228				return errors.New("someerror")
229			default:
230				t.Fatalf("unexpected command: %#v", cmd)
231				return nil
232			}
233		}
234		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
235		if err := verifyInternalError(stderr); err != nil {
236			t.Error(err)
237		}
238		if !strings.Contains(stderr, "someerror") {
239			t.Errorf("unexpected stderr. Got: %s", stderr)
240		}
241	})
242}
243
244func TestOmitLogWarningsIfNoDoubleBuild(t *testing.T) {
245	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
246		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
247		if ctx.cmdCount != 1 {
248			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
249		}
250		if loggedWarnings := readLoggedWarnings(ctx); loggedWarnings != nil {
251			t.Errorf("expected no logged warnings. Got: %#v", loggedWarnings)
252		}
253	})
254}
255
256func TestLogWarningsWhenDoubleBuildSucceeds(t *testing.T) {
257	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
258		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
259			switch ctx.cmdCount {
260			case 1:
261				fmt.Fprint(stdout, "originalmessage")
262				fmt.Fprint(stderr, "-Werror originalerror")
263				return newExitCodeError(1)
264			case 2:
265				fmt.Fprint(stdout, "retrymessage")
266				fmt.Fprint(stderr, "retryerror")
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(clangX86_64, mainCc)))
274		loggedWarnings := readLoggedWarnings(ctx)
275		if loggedWarnings == nil {
276			t.Fatal("expected logged warnings")
277		}
278		if loggedWarnings.Cwd != ctx.getwd() {
279			t.Fatalf("unexpected cwd. Got: %s", loggedWarnings.Cwd)
280		}
281		loggedCmd := &command{
282			Path: loggedWarnings.Command[0],
283			Args: loggedWarnings.Command[1:],
284		}
285		if err := verifyPath(loggedCmd, "usr/bin/clang"); err != nil {
286			t.Error(err)
287		}
288		if err := verifyArgOrder(loggedCmd, "--sysroot=.*", mainCc); err != nil {
289			t.Error(err)
290		}
291	})
292}
293
294func TestLogWarningsWhenDoubleBuildFails(t *testing.T) {
295	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
296		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
297			switch ctx.cmdCount {
298			case 1:
299				fmt.Fprint(stdout, "originalmessage")
300				fmt.Fprint(stderr, "-Werror originalerror")
301				return newExitCodeError(1)
302			case 2:
303				fmt.Fprint(stdout, "retrymessage")
304				fmt.Fprint(stderr, "retryerror")
305				return newExitCodeError(1)
306			default:
307				t.Fatalf("unexpected command: %#v", cmd)
308				return nil
309			}
310		}
311		ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
312		loggedWarnings := readLoggedWarnings(ctx)
313		if loggedWarnings != nil {
314			t.Fatal("expected no warnings to be logged")
315		}
316	})
317}
318
319func withForceDisableWErrorTestContext(t *testing.T, work func(ctx *testContext)) {
320	withTestContext(t, func(ctx *testContext) {
321		ctx.env = []string{"FORCE_DISABLE_WERROR=1"}
322		work(ctx)
323	})
324}
325
326func readLoggedWarnings(ctx *testContext) *warningsJSONData {
327	files, err := ioutil.ReadDir(ctx.cfg.newWarningsDir)
328	if err != nil {
329		if _, ok := err.(*os.PathError); ok {
330			return nil
331		}
332		ctx.t.Fatal(err)
333	}
334	if len(files) != 1 {
335		ctx.t.Fatalf("expected 1 warning log file. Got: %s", files)
336	}
337	data, err := ioutil.ReadFile(filepath.Join(ctx.cfg.newWarningsDir, files[0].Name()))
338	if err != nil {
339		ctx.t.Fatal(err)
340	}
341	jsonData := warningsJSONData{}
342	if err := json.Unmarshal(data, &jsonData); err != nil {
343		ctx.t.Fatal(err)
344	}
345	return &jsonData
346}
347
348func TestDoubleBuildWerrorChmodsThingsAppropriately(t *testing.T) {
349	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
350		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
351			switch ctx.cmdCount {
352			case 1:
353				if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil {
354					return err
355				}
356				fmt.Fprint(stderr, "-Werror originalerror")
357				return newExitCodeError(1)
358			case 2:
359				if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil {
360					return err
361				}
362				return nil
363			default:
364				t.Fatalf("unexpected command: %#v", cmd)
365				return nil
366			}
367		}
368		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
369		if ctx.cmdCount != 2 {
370			// Later errors are likely senseless if we didn't get called twice.
371			t.Fatalf("expected 2 calls. Got: %d", ctx.cmdCount)
372		}
373
374		t.Logf("Warnings dir is at %q", ctx.cfg.newWarningsDir)
375		warningsDir, err := os.Open(ctx.cfg.newWarningsDir)
376		if err != nil {
377			t.Fatalf("failed to open the new warnings dir: %v", err)
378		}
379		defer warningsDir.Close()
380
381		fi, err := warningsDir.Stat()
382		if err != nil {
383			t.Fatalf("failed stat'ing the warnings dir: %v", err)
384		}
385
386		permBits := func(mode os.FileMode) int { return int(mode & 0777) }
387
388		if perms := permBits(fi.Mode()); perms != 0777 {
389			t.Errorf("mode for tempdir are %#o; expected 0777", perms)
390		}
391
392		entries, err := warningsDir.Readdir(0)
393		if err != nil {
394			t.Fatalf("failed reading entries of the tempdir: %v", err)
395		}
396
397		if len(entries) != 1 {
398			t.Errorf("found %d tempfiles in the tempdir; expected 1", len(entries))
399		}
400
401		for _, e := range entries {
402			if perms := permBits(e.Mode()); perms != 0666 {
403				t.Errorf("mode for %q is %#o; expected 0666", e.Name(), perms)
404			}
405		}
406	})
407}
408
409func TestAndroidDisableWerror(t *testing.T) {
410	withTestContext(t, func(ctx *testContext) {
411		ctx.cfg.isAndroidWrapper = true
412
413		// Disable werror ON
414		ctx.cfg.useLlvmNext = true
415		if !shouldForceDisableWerror(ctx, ctx.cfg) {
416			t.Errorf("disable Werror not enabled for Android with useLlvmNext")
417		}
418
419		// Disable werror OFF
420		ctx.cfg.useLlvmNext = false
421		if shouldForceDisableWerror(ctx, ctx.cfg) {
422			t.Errorf("disable-Werror enabled for Android without useLlvmNext")
423		}
424	})
425}
426
427func TestChromeOSNoForceDisableWerror(t *testing.T) {
428	withTestContext(t, func(ctx *testContext) {
429		if shouldForceDisableWerror(ctx, ctx.cfg) {
430			t.Errorf("disable Werror enabled for ChromeOS without FORCE_DISABLE_WERROR set")
431		}
432	})
433}
434
435func TestClangTidyNoDoubleBuild(t *testing.T) {
436	withTestContext(t, func(ctx *testContext) {
437		ctx.cfg.isAndroidWrapper = true
438		ctx.cfg.useLlvmNext = true
439		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "--", mainCc)))
440		if ctx.cmdCount != 1 {
441			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
442		}
443	})
444}
445
446func withAndroidClangTidyTestContext(t *testing.T, work func(ctx *testContext)) {
447	withTestContext(t, func(ctx *testContext) {
448		ctx.cfg.isAndroidWrapper = true
449		ctx.cfg.useLlvmNext = true
450		ctx.env = []string{"OUT_DIR=/tmp"}
451
452		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
453			hasArg := func(s string) bool {
454				for _, e := range cmd.Args {
455					if strings.Contains(e, s) {
456						return true
457					}
458				}
459				return false
460			}
461			switch ctx.cmdCount {
462			case 1:
463				if hasArg("-Werror") {
464					fmt.Fprint(stdout, "clang-diagnostic-")
465					return newExitCodeError(1)
466				}
467				if hasArg("-warnings-as-errors") {
468					fmt.Fprint(stdout, "warnings-as-errors")
469					return newExitCodeError(1)
470				}
471				return nil
472			case 2:
473				if hasArg("warnings-as-errors") {
474					return fmt.Errorf("Unexpected arg warnings-as-errors found.  All args: %s", cmd.Args)
475				}
476				return nil
477			default:
478				t.Fatalf("unexpected command: %#v", cmd)
479				return nil
480			}
481		}
482		work(ctx)
483	})
484}
485
486func TestClangTidyDoubleBuildClangTidyError(t *testing.T) {
487	withAndroidClangTidyTestContext(t, func(ctx *testContext) {
488		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-warnings-as-errors=*", "--", mainCc)))
489		if ctx.cmdCount != 2 {
490			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
491		}
492	})
493}
494
495func TestClangTidyDoubleBuildClangError(t *testing.T) {
496	withAndroidClangTidyTestContext(t, func(ctx *testContext) {
497		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-Werrors=*", "--", mainCc)))
498		if ctx.cmdCount != 2 {
499			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
500		}
501	})
502}
503
504func TestProcPidStatParsingWorksAsIntended(t *testing.T) {
505	t.Parallel()
506
507	type expected struct {
508		parent int
509		ok     bool
510	}
511
512	testCases := []struct {
513		input    string
514		expected expected
515	}{
516		{
517			input: "2556041 (cat) R 2519408 2556041 2519408 34818 2556041 4194304",
518			expected: expected{
519				parent: 2519408,
520				ok:     true,
521			},
522		},
523		{
524			input: "2556041 (c a t) R 2519408 2556041 2519408 34818 2556041 4194304",
525			expected: expected{
526				parent: 2519408,
527				ok:     true,
528			},
529		},
530		{
531			input: "",
532			expected: expected{
533				ok: false,
534			},
535		},
536		{
537			input: "foo (bar)",
538			expected: expected{
539				ok: false,
540			},
541		},
542		{
543			input: "foo (bar) baz",
544			expected: expected{
545				ok: false,
546			},
547		},
548		{
549			input: "foo (bar) baz 1qux2",
550			expected: expected{
551				ok: false,
552			},
553		},
554	}
555
556	for _, tc := range testCases {
557		parent, ok := parseParentPidFromPidStat(tc.input)
558		if tc.expected.ok != ok {
559			t.Errorf("Got ok=%v when parsing %q; expected %v", ok, tc.input, tc.expected.ok)
560			continue
561		}
562
563		if tc.expected.parent != parent {
564			t.Errorf("Got parent=%v when parsing %q; expected %v", parent, tc.input, tc.expected.parent)
565		}
566	}
567}
568