• 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	"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.NoteTestWritesToUmask()
322
323		ctx.env = []string{"FORCE_DISABLE_WERROR=1"}
324		work(ctx)
325	})
326}
327
328func readLoggedWarnings(ctx *testContext) *warningsJSONData {
329	files, err := ioutil.ReadDir(ctx.cfg.newWarningsDir)
330	if err != nil {
331		if _, ok := err.(*os.PathError); ok {
332			return nil
333		}
334		ctx.t.Fatal(err)
335	}
336	if len(files) != 1 {
337		ctx.t.Fatalf("expected 1 warning log file. Got: %s", files)
338	}
339	data, err := ioutil.ReadFile(filepath.Join(ctx.cfg.newWarningsDir, files[0].Name()))
340	if err != nil {
341		ctx.t.Fatal(err)
342	}
343	jsonData := warningsJSONData{}
344	if err := json.Unmarshal(data, &jsonData); err != nil {
345		ctx.t.Fatal(err)
346	}
347	return &jsonData
348}
349
350func TestDoubleBuildWerrorChmodsThingsAppropriately(t *testing.T) {
351	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
352		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
353			switch ctx.cmdCount {
354			case 1:
355				if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil {
356					return err
357				}
358				fmt.Fprint(stderr, "-Werror originalerror")
359				return newExitCodeError(1)
360			case 2:
361				if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil {
362					return err
363				}
364				return nil
365			default:
366				t.Fatalf("unexpected command: %#v", cmd)
367				return nil
368			}
369		}
370		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
371		if ctx.cmdCount != 2 {
372			// Later errors are likely senseless if we didn't get called twice.
373			t.Fatalf("expected 2 calls. Got: %d", ctx.cmdCount)
374		}
375
376		t.Logf("Warnings dir is at %q", ctx.cfg.newWarningsDir)
377		warningsDir, err := os.Open(ctx.cfg.newWarningsDir)
378		if err != nil {
379			t.Fatalf("failed to open the new warnings dir: %v", err)
380		}
381		defer warningsDir.Close()
382
383		fi, err := warningsDir.Stat()
384		if err != nil {
385			t.Fatalf("failed stat'ing the warnings dir: %v", err)
386		}
387
388		permBits := func(mode os.FileMode) int { return int(mode & 0777) }
389
390		if perms := permBits(fi.Mode()); perms != 0777 {
391			t.Errorf("mode for tempdir are %#o; expected 0777", perms)
392		}
393
394		entries, err := warningsDir.Readdir(0)
395		if err != nil {
396			t.Fatalf("failed reading entries of the tempdir: %v", err)
397		}
398
399		if len(entries) != 1 {
400			t.Errorf("found %d tempfiles in the tempdir; expected 1", len(entries))
401		}
402
403		for _, e := range entries {
404			if perms := permBits(e.Mode()); perms != 0666 {
405				t.Errorf("mode for %q is %#o; expected 0666", e.Name(), perms)
406			}
407		}
408	})
409}
410
411func TestAndroidDisableWerror(t *testing.T) {
412	withTestContext(t, func(ctx *testContext) {
413		ctx.cfg.isAndroidWrapper = true
414
415		// Disable werror ON
416		ctx.cfg.useLlvmNext = true
417		if !shouldForceDisableWerror(ctx, ctx.cfg, gccType) {
418			t.Errorf("disable Werror not enabled for Android with useLlvmNext")
419		}
420
421		if !shouldForceDisableWerror(ctx, ctx.cfg, clangType) {
422			t.Errorf("disable Werror not enabled for Android with useLlvmNext")
423		}
424
425		// Disable werror OFF
426		ctx.cfg.useLlvmNext = false
427		if shouldForceDisableWerror(ctx, ctx.cfg, gccType) {
428			t.Errorf("disable-Werror enabled for Android without useLlvmNext")
429		}
430
431		if shouldForceDisableWerror(ctx, ctx.cfg, clangType) {
432			t.Errorf("disable-Werror enabled for Android without useLlvmNext")
433		}
434	})
435}
436
437func TestChromeOSNoForceDisableWerror(t *testing.T) {
438	withTestContext(t, func(ctx *testContext) {
439		if shouldForceDisableWerror(ctx, ctx.cfg, gccType) {
440			t.Errorf("disable Werror enabled for ChromeOS without FORCE_DISABLE_WERROR set")
441		}
442
443		if shouldForceDisableWerror(ctx, ctx.cfg, clangType) {
444			t.Errorf("disable Werror enabled for ChromeOS without FORCE_DISABLE_WERROR set")
445		}
446	})
447}
448
449func TestChromeOSForceDisableWerrorOnlyAppliesToClang(t *testing.T) {
450	withForceDisableWErrorTestContext(t, func(ctx *testContext) {
451		if !shouldForceDisableWerror(ctx, ctx.cfg, clangType) {
452			t.Errorf("Disable -Werror should be enabled for clang.")
453		}
454
455		if shouldForceDisableWerror(ctx, ctx.cfg, gccType) {
456			t.Errorf("Disable -Werror should be disabled for gcc.")
457		}
458	})
459}
460
461func TestClangTidyNoDoubleBuild(t *testing.T) {
462	withTestContext(t, func(ctx *testContext) {
463		ctx.cfg.isAndroidWrapper = true
464		ctx.cfg.useLlvmNext = true
465		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "--", mainCc)))
466		if ctx.cmdCount != 1 {
467			t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
468		}
469	})
470}
471
472func withAndroidClangTidyTestContext(t *testing.T, work func(ctx *testContext)) {
473	withTestContext(t, func(ctx *testContext) {
474		ctx.cfg.isAndroidWrapper = true
475		ctx.cfg.useLlvmNext = true
476		ctx.env = []string{"OUT_DIR=/tmp"}
477
478		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
479			hasArg := func(s string) bool {
480				for _, e := range cmd.Args {
481					if strings.Contains(e, s) {
482						return true
483					}
484				}
485				return false
486			}
487			switch ctx.cmdCount {
488			case 1:
489				if hasArg("-Werror") {
490					fmt.Fprint(stdout, "clang-diagnostic-")
491					return newExitCodeError(1)
492				}
493				if hasArg("-warnings-as-errors") {
494					fmt.Fprint(stdout, "warnings-as-errors")
495					return newExitCodeError(1)
496				}
497				return nil
498			case 2:
499				if hasArg("warnings-as-errors") {
500					return fmt.Errorf("Unexpected arg warnings-as-errors found.  All args: %s", cmd.Args)
501				}
502				return nil
503			default:
504				t.Fatalf("unexpected command: %#v", cmd)
505				return nil
506			}
507		}
508		work(ctx)
509	})
510}
511
512func TestClangTidyDoubleBuildClangTidyError(t *testing.T) {
513	withAndroidClangTidyTestContext(t, func(ctx *testContext) {
514		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-warnings-as-errors=*", "--", mainCc)))
515		if ctx.cmdCount != 2 {
516			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
517		}
518	})
519}
520
521func TestClangTidyDoubleBuildClangError(t *testing.T) {
522	withAndroidClangTidyTestContext(t, func(ctx *testContext) {
523		ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-Werrors=*", "--", mainCc)))
524		if ctx.cmdCount != 2 {
525			t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
526		}
527	})
528}
529
530func TestProcPidStatParsingWorksAsIntended(t *testing.T) {
531	t.Parallel()
532
533	type expected struct {
534		parent int
535		ok     bool
536	}
537
538	testCases := []struct {
539		input    string
540		expected expected
541	}{
542		{
543			input: "2556041 (cat) R 2519408 2556041 2519408 34818 2556041 4194304",
544			expected: expected{
545				parent: 2519408,
546				ok:     true,
547			},
548		},
549		{
550			input: "2556041 (c a t) R 2519408 2556041 2519408 34818 2556041 4194304",
551			expected: expected{
552				parent: 2519408,
553				ok:     true,
554			},
555		},
556		{
557			input: "",
558			expected: expected{
559				ok: false,
560			},
561		},
562		{
563			input: "foo (bar)",
564			expected: expected{
565				ok: false,
566			},
567		},
568		{
569			input: "foo (bar) baz",
570			expected: expected{
571				ok: false,
572			},
573		},
574		{
575			input: "foo (bar) baz 1qux2",
576			expected: expected{
577				ok: false,
578			},
579		},
580	}
581
582	for _, tc := range testCases {
583		parent, ok := parseParentPidFromPidStat(tc.input)
584		if tc.expected.ok != ok {
585			t.Errorf("Got ok=%v when parsing %q; expected %v", ok, tc.input, tc.expected.ok)
586			continue
587		}
588
589		if tc.expected.parent != parent {
590			t.Errorf("Got parent=%v when parsing %q; expected %v", parent, tc.input, tc.expected.parent)
591		}
592	}
593}
594