blob: dc9710ac7ec952276df1c41e41e9c8500081e8bd [file] [log] [blame]
// Copyright 2021 The Fuchsia 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 fint
import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestRunNinja(t *testing.T) {
ctx := context.Background()
testCases := []struct {
name string
// Whether to mock a non-zero ninja retcode, in which case we should get
// an error.
fail bool
// Mock Ninja stdout.
stdout string
expectedFailureMessage string
}{
{
name: "success",
stdout: `
[1/2] ACTION a.o
[2/2] ACTION b.o
`,
},
{
name: "single failed target",
fail: true,
stdout: `
[35792/53672] CXX a.o b.o
[35793/53672] CXX c.o d.o
FAILED: c.o d.o
output line 1
output line 2
[35794/53672] CXX successful/e.o
[35795/53672] CXX f.o
`,
expectedFailureMessage: `
[35793/53672] CXX c.o d.o
FAILED: c.o d.o
output line 1
output line 2
`,
},
{
name: "preserves indentation",
fail: true,
stdout: `
[35793/53672] CXX a.o b.o
FAILED: a.o b.o
output line 1
output line 2
output line 3
[35794/53672] CXX successful/c.o
`,
expectedFailureMessage: `
[35793/53672] CXX a.o b.o
FAILED: a.o b.o
output line 1
output line 2
output line 3
`,
},
{
name: "multiple failed targets",
fail: true,
stdout: `
[35790/53672] CXX foo
[35791/53672] CXX a.o b.o
FAILED: a.o b.o
output line 1
output line 2
[35792/53672] CXX c.o d.o
[35793/53673] CXX e.o
FAILED: e.o
output line 3
output line 4
[35794/53672] CXX f.o
`,
expectedFailureMessage: `
[35791/53672] CXX a.o b.o
FAILED: a.o b.o
output line 1
output line 2
[35793/53673] CXX e.o
FAILED: e.o
output line 3
output line 4
`,
},
{
name: "last target fails",
fail: true,
stdout: `
[35790/53672] CXX foo
[35791/53672] CXX a.o b.o
FAILED: a.o b.o
output line 1
output line 2
ninja: build stopped: subcommand failed.
`,
expectedFailureMessage: `
[35791/53672] CXX a.o b.o
FAILED: a.o b.o
output line 1
output line 2
`,
},
{
name: "graph error",
fail: true,
stdout: `
ninja: Entering directory /foo
ninja: error: bar.ninja: multiple rules generate baz
`,
expectedFailureMessage: `
ninja: error: bar.ninja: multiple rules generate baz
`,
},
{
name: "unrecognized failure",
fail: true,
stdout: `
ninja: Entering directory /foo
...something went wrong...
`,
expectedFailureMessage: unrecognizedFailureMsg,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.stdout = normalize(tc.stdout)
tc.expectedFailureMessage = normalize(tc.expectedFailureMessage)
sr := &fakeSubprocessRunner{
mockStdout: []byte(tc.stdout),
fail: tc.fail,
}
r := ninjaRunner{
runner: sr,
ninjaPath: filepath.Join(t.TempDir(), "ninja"),
buildDir: filepath.Join(t.TempDir(), "out"),
jobCount: 23, // Arbitrary but distinctive value.
}
msg, err := runNinja(ctx, r, []string{"foo", "bar"})
if tc.fail {
if !errors.Is(err, errSubprocessFailure) {
t.Fatalf("Expected a subprocess failure error but got: %s", err)
}
} else if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if len(sr.commandsRun) != 1 {
t.Fatalf("expected runNinja to run 1 command but got %d", len(sr.commandsRun))
}
cmd := sr.commandsRun[0]
if cmd[0] != r.ninjaPath {
t.Fatalf("runNinja ran wrong executable %q (expected %q)", cmd[0], r.ninjaPath)
}
foundJobCount := false
for i, part := range cmd {
if part == "-j" {
foundJobCount = true
if i+1 >= len(cmd) || cmd[i+1] != fmt.Sprintf("%d", r.jobCount) {
t.Errorf("wrong value for -j flag: %v", cmd)
}
}
}
if !foundJobCount {
t.Errorf("runNinja didn't set the -j flag. Full command: %v", cmd)
}
if diff := cmp.Diff(tc.expectedFailureMessage, msg); diff != "" {
t.Errorf("Unexpected failure message diff (-want +got):\n%s", diff)
}
})
}
}
// normalize removes a leading newline and trailing spaces from a multiline
// string, ensuring that the expected failure message has the same whitespace
// formatting as failure messages emitted by runNinja.
func normalize(s string) string {
s = strings.TrimLeft(s, "\n")
s = strings.TrimRight(s, " ")
return s
}
func TestCheckNinjaNoop(t *testing.T) {
testCases := []struct {
name string
isMac bool
stdout string
expectNoop bool
}{
{
name: "no-op",
stdout: "ninja: Entering directory /foo\nninja: no work to do.",
expectNoop: true,
},
{
name: "dirty",
stdout: "ninja: Entering directory /foo\n[1/1] STAMP foo.stamp",
expectNoop: false,
},
{
name: "mac dirty",
isMac: true,
stdout: "ninja: Entering directory /foo\n[1/1] STAMP foo.stamp",
expectNoop: false,
},
{
name: "broken mac path",
isMac: true,
stdout: "ninja: Entering directory /foo\nninja explain: ../../../../usr/bin/env is dirty",
expectNoop: true,
},
}
ctx := context.Background()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := ninjaRunner{
runner: &fakeSubprocessRunner{
mockStdout: []byte(tc.stdout),
},
ninjaPath: "ninja",
buildDir: t.TempDir(),
}
noop, err := checkNinjaNoop(ctx, r, []string{"foo"}, tc.isMac)
if err != nil {
t.Fatal(err)
}
if noop != tc.expectNoop {
t.Errorf("Unexpected ninja no-op result: got %v, expected %v", noop, tc.expectNoop)
}
})
}
}
func TestNinjaGraph(t *testing.T) {
ctx := context.Background()
stdout := "ninja\ngraph\nstdout"
r := ninjaRunner{
runner: &fakeSubprocessRunner{
mockStdout: []byte(stdout),
},
ninjaPath: "ninja",
buildDir: t.TempDir(),
}
path, err := ninjaGraph(ctx, r, []string{"foo", "bar"})
if err != nil {
t.Fatal(err)
}
defer os.Remove(path)
fileContentsBytes, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}
fileContents := string(fileContentsBytes)
if diff := cmp.Diff(stdout, fileContents); diff != "" {
t.Errorf("Unexpected ninja graph file diff (-want +got):\n%s", diff)
}
}
func TestNinjaCompdb(t *testing.T) {
ctx := context.Background()
stdout := "ninja\ncompdb\nstdout"
r := ninjaRunner{
runner: &fakeSubprocessRunner{
mockStdout: []byte(stdout),
},
ninjaPath: "ninja",
buildDir: t.TempDir(),
}
path, err := ninjaCompdb(ctx, r)
if err != nil {
t.Fatal(err)
}
defer os.Remove(path)
fileContentsBytes, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}
fileContents := string(fileContentsBytes)
if diff := cmp.Diff(stdout, fileContents); diff != "" {
t.Errorf("Unexpected ninja compdb file diff (-want +got):\n%s", diff)
}
}