blob: ecc1090bc04dc5f052e3bc87d97f438f0c32698b [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
)
func TestOmitDoubleBuildForSuccessfulCall(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if ctx.cmdCount != 1 {
t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
}
})
}
func TestOmitDoubleBuildForGeneralError(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
return errors.New("someerror")
}
stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if err := verifyInternalError(stderr); err != nil {
t.Fatal(err)
}
if !strings.Contains(stderr, "someerror") {
t.Errorf("unexpected error. Got: %s", stderr)
}
if ctx.cmdCount != 1 {
t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
}
})
}
func TestDoubleBuildWithWNoErrorFlag(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil {
return err
}
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
case 2:
if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil {
return err
}
return nil
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if ctx.cmdCount != 2 {
t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
}
})
}
func TestKnownConfigureFileParsing(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
for _, f := range []string{"conftest.c", "conftest.cpp", "/dev/null"} {
if !isLikelyAConfTest(ctx.cfg, ctx.newCommand(clangX86_64, f)) {
t.Errorf("%q isn't considered a conf test file", f)
}
}
})
}
func TestDoubleBuildWithKnownConfigureFile(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil {
return err
}
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "conftest.c")))
if ctx.cmdCount != 1 {
t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
}
})
}
func TestDoubleBuildWithWNoErrorAndCCache(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cfg.useCCache = true
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
// TODO: This is a bug in the old wrapper that it drops the ccache path
// during double build. Fix this once we don't compare to the old wrapper anymore.
if err := verifyPath(cmd, "ccache"); err != nil {
return err
}
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
case 2:
if err := verifyPath(cmd, "ccache"); err != nil {
return err
}
return nil
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if ctx.cmdCount != 2 {
t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
}
})
}
func TestForwardStdoutAndStderrWhenDoubleBuildSucceeds(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
fmt.Fprint(stdout, "originalmessage")
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
case 2:
fmt.Fprint(stdout, "retrymessage")
fmt.Fprint(stderr, "retryerror")
return nil
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if err := verifyNonInternalError(ctx.stderrString(), "retryerror"); err != nil {
t.Error(err)
}
if !strings.Contains(ctx.stdoutString(), "retrymessage") {
t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString())
}
})
}
func TestForwardStdoutAndStderrWhenDoubleBuildFails(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
fmt.Fprint(stdout, "originalmessage")
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(3)
case 2:
fmt.Fprint(stdout, "retrymessage")
fmt.Fprint(stderr, "retryerror")
return newExitCodeError(5)
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc))
if exitCode != 3 {
t.Errorf("unexpected exitcode. Got: %d", exitCode)
}
if err := verifyNonInternalError(ctx.stderrString(), "-Werror originalerror"); err != nil {
t.Error(err)
}
if !strings.Contains(ctx.stdoutString(), "originalmessage") {
t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString())
}
})
}
func TestForwardStdinFromDoubleBuild(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
// Note: This is called for the clang syntax call as well as for
// the gcc call, and we assert that stdin is cloned and forwarded
// to both.
stdinStr := ctx.readAllString(stdin)
if stdinStr != "someinput" {
return fmt.Errorf("unexpected stdin. Got: %s", stdinStr)
}
switch ctx.cmdCount {
case 1:
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
case 2:
return nil
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
io.WriteString(&ctx.stdinBuffer, "someinput")
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "-", mainCc)))
})
}
func TestForwardGeneralErrorWhenDoubleBuildFails(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(3)
case 2:
return errors.New("someerror")
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if err := verifyInternalError(stderr); err != nil {
t.Error(err)
}
if !strings.Contains(stderr, "someerror") {
t.Errorf("unexpected stderr. Got: %s", stderr)
}
})
}
func TestOmitLogWarningsIfNoDoubleBuild(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if ctx.cmdCount != 1 {
t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
}
if loggedWarnings := readLoggedWarnings(ctx); loggedWarnings != nil {
t.Errorf("expected no logged warnings. Got: %#v", loggedWarnings)
}
})
}
func TestLogWarningsWhenDoubleBuildSucceeds(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
fmt.Fprint(stdout, "originalmessage")
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
case 2:
fmt.Fprint(stdout, "retrymessage")
fmt.Fprint(stderr, "retryerror")
return nil
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
loggedWarnings := readLoggedWarnings(ctx)
if loggedWarnings == nil {
t.Fatal("expected logged warnings")
}
if loggedWarnings.Cwd != ctx.getwd() {
t.Fatalf("unexpected cwd. Got: %s", loggedWarnings.Cwd)
}
loggedCmd := &command{
Path: loggedWarnings.Command[0],
Args: loggedWarnings.Command[1:],
}
if err := verifyPath(loggedCmd, "usr/bin/clang"); err != nil {
t.Error(err)
}
if err := verifyArgOrder(loggedCmd, "--sysroot=.*", mainCc); err != nil {
t.Error(err)
}
})
}
func TestLogWarningsWhenDoubleBuildFails(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
fmt.Fprint(stdout, "originalmessage")
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
case 2:
fmt.Fprint(stdout, "retrymessage")
fmt.Fprint(stderr, "retryerror")
return newExitCodeError(1)
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
loggedWarnings := readLoggedWarnings(ctx)
if loggedWarnings != nil {
t.Fatal("expected no warnings to be logged")
}
})
}
func withForceDisableWErrorTestContext(t *testing.T, work func(ctx *testContext)) {
withTestContext(t, func(ctx *testContext) {
ctx.env = []string{"FORCE_DISABLE_WERROR=1"}
work(ctx)
})
}
func readLoggedWarnings(ctx *testContext) *warningsJSONData {
files, err := ioutil.ReadDir(ctx.cfg.newWarningsDir)
if err != nil {
if _, ok := err.(*os.PathError); ok {
return nil
}
ctx.t.Fatal(err)
}
if len(files) != 1 {
ctx.t.Fatalf("expected 1 warning log file. Got: %s", files)
}
data, err := ioutil.ReadFile(filepath.Join(ctx.cfg.newWarningsDir, files[0].Name()))
if err != nil {
ctx.t.Fatal(err)
}
jsonData := warningsJSONData{}
if err := json.Unmarshal(data, &jsonData); err != nil {
ctx.t.Fatal(err)
}
return &jsonData
}
func TestDoubleBuildWerrorChmodsThingsAppropriately(t *testing.T) {
withForceDisableWErrorTestContext(t, func(ctx *testContext) {
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
switch ctx.cmdCount {
case 1:
if err := verifyArgCount(cmd, 0, "-Wno-error"); err != nil {
return err
}
fmt.Fprint(stderr, "-Werror originalerror")
return newExitCodeError(1)
case 2:
if err := verifyArgCount(cmd, 1, "-Wno-error"); err != nil {
return err
}
return nil
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, mainCc)))
if ctx.cmdCount != 2 {
// Later errors are likely senseless if we didn't get called twice.
t.Fatalf("expected 2 calls. Got: %d", ctx.cmdCount)
}
t.Logf("Warnings dir is at %q", ctx.cfg.newWarningsDir)
warningsDir, err := os.Open(ctx.cfg.newWarningsDir)
if err != nil {
t.Fatalf("failed to open the new warnings dir: %v", err)
}
defer warningsDir.Close()
fi, err := warningsDir.Stat()
if err != nil {
t.Fatalf("failed stat'ing the warnings dir: %v", err)
}
permBits := func(mode os.FileMode) int { return int(mode & 0777) }
if perms := permBits(fi.Mode()); perms != 0777 {
t.Errorf("mode for tempdir are %#o; expected 0777", perms)
}
entries, err := warningsDir.Readdir(0)
if err != nil {
t.Fatalf("failed reading entries of the tempdir: %v", err)
}
if len(entries) != 1 {
t.Errorf("found %d tempfiles in the tempdir; expected 1", len(entries))
}
for _, e := range entries {
if perms := permBits(e.Mode()); perms != 0666 {
t.Errorf("mode for %q is %#o; expected 0666", e.Name(), perms)
}
}
})
}
func TestAndroidDisableWerror(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.isAndroidWrapper = true
// Disable werror ON
ctx.cfg.useLlvmNext = true
if !shouldForceDisableWerror(ctx, ctx.cfg) {
t.Errorf("disable Werror not enabled for Android with useLlvmNext")
}
// Disable werror OFF
ctx.cfg.useLlvmNext = false
if shouldForceDisableWerror(ctx, ctx.cfg) {
t.Errorf("disable-Werror enabled for Android without useLlvmNext")
}
})
}
func TestChromeOSNoForceDisableWerror(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
if shouldForceDisableWerror(ctx, ctx.cfg) {
t.Errorf("disable Werror enabled for ChromeOS without FORCE_DISABLE_WERROR set")
}
})
}
func TestClangTidyNoDoubleBuild(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.isAndroidWrapper = true
ctx.cfg.useLlvmNext = true
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "--", mainCc)))
if ctx.cmdCount != 1 {
t.Errorf("expected 1 call. Got: %d", ctx.cmdCount)
}
})
}
func withAndroidClangTidyTestContext(t *testing.T, work func(ctx *testContext)) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.isAndroidWrapper = true
ctx.cfg.useLlvmNext = true
ctx.env = []string{"OUT_DIR=/tmp"}
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
hasArg := func(s string) bool {
for _, e := range cmd.Args {
if strings.Contains(e, s) {
return true
}
}
return false
}
switch ctx.cmdCount {
case 1:
if hasArg("-Werror") {
fmt.Fprint(stdout, "clang-diagnostic-")
return newExitCodeError(1)
}
if hasArg("-warnings-as-errors") {
fmt.Fprint(stdout, "warnings-as-errors")
return newExitCodeError(1)
}
return nil
case 2:
if hasArg("warnings-as-errors") {
return fmt.Errorf("Unexpected arg warnings-as-errors found. All args: %s", cmd.Args)
}
return nil
default:
t.Fatalf("unexpected command: %#v", cmd)
return nil
}
}
work(ctx)
})
}
func TestClangTidyDoubleBuildClangTidyError(t *testing.T) {
withAndroidClangTidyTestContext(t, func(ctx *testContext) {
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-warnings-as-errors=*", "--", mainCc)))
if ctx.cmdCount != 2 {
t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
}
})
}
func TestClangTidyDoubleBuildClangError(t *testing.T) {
withAndroidClangTidyTestContext(t, func(ctx *testContext) {
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangTidyAndroid, "-Werrors=*", "--", mainCc)))
if ctx.cmdCount != 2 {
t.Errorf("expected 2 calls. Got: %d", ctx.cmdCount)
}
})
}