You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
7.7 KiB
299 lines
7.7 KiB
// 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 (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
mainCc = "main.cc"
|
|
clangAndroid = "./clang"
|
|
clangTidyAndroid = "./clang-tidy"
|
|
clangX86_64 = "./x86_64-cros-linux-gnu-clang"
|
|
gccX86_64 = "./x86_64-cros-linux-gnu-gcc"
|
|
gccX86_64Eabi = "./x86_64-cros-eabi-gcc"
|
|
gccArmV7 = "./armv7m-cros-linux-gnu-gcc"
|
|
gccArmV7Eabi = "./armv7m-cros-eabi-gcc"
|
|
gccArmV8 = "./armv8m-cros-linux-gnu-gcc"
|
|
gccArmV8Eabi = "./armv8m-cros-eabi-gcc"
|
|
)
|
|
|
|
type testContext struct {
|
|
t *testing.T
|
|
wd string
|
|
tempDir string
|
|
env []string
|
|
cfg *config
|
|
inputCmd *command
|
|
lastCmd *command
|
|
cmdCount int
|
|
cmdMock func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error
|
|
stdinBuffer bytes.Buffer
|
|
stdoutBuffer bytes.Buffer
|
|
stderrBuffer bytes.Buffer
|
|
}
|
|
|
|
func withTestContext(t *testing.T, work func(ctx *testContext)) {
|
|
t.Parallel()
|
|
tempDir, err := ioutil.TempDir("", "compiler_wrapper")
|
|
if err != nil {
|
|
t.Fatalf("Unable to create the temp dir. Error: %s", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
ctx := testContext{
|
|
t: t,
|
|
wd: tempDir,
|
|
tempDir: tempDir,
|
|
env: nil,
|
|
cfg: &config{},
|
|
}
|
|
ctx.updateConfig(&config{})
|
|
|
|
work(&ctx)
|
|
}
|
|
|
|
var _ env = (*testContext)(nil)
|
|
|
|
func (ctx *testContext) getenv(key string) (string, bool) {
|
|
for i := len(ctx.env) - 1; i >= 0; i-- {
|
|
entry := ctx.env[i]
|
|
if strings.HasPrefix(entry, key+"=") {
|
|
return entry[len(key)+1:], true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (ctx *testContext) environ() []string {
|
|
return ctx.env
|
|
}
|
|
|
|
func (ctx *testContext) getwd() string {
|
|
return ctx.wd
|
|
}
|
|
|
|
func (ctx *testContext) stdin() io.Reader {
|
|
return &ctx.stdinBuffer
|
|
}
|
|
|
|
func (ctx *testContext) stdout() io.Writer {
|
|
return &ctx.stdoutBuffer
|
|
}
|
|
|
|
func (ctx *testContext) stdoutString() string {
|
|
return ctx.stdoutBuffer.String()
|
|
}
|
|
|
|
func (ctx *testContext) stderr() io.Writer {
|
|
return &ctx.stderrBuffer
|
|
}
|
|
|
|
func (ctx *testContext) stderrString() string {
|
|
return ctx.stderrBuffer.String()
|
|
}
|
|
|
|
func (ctx *testContext) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
|
|
ctx.cmdCount++
|
|
ctx.lastCmd = cmd
|
|
if ctx.cmdMock != nil {
|
|
return ctx.cmdMock(cmd, stdin, stdout, stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ctx *testContext) exec(cmd *command) error {
|
|
ctx.cmdCount++
|
|
ctx.lastCmd = cmd
|
|
if ctx.cmdMock != nil {
|
|
return ctx.cmdMock(cmd, ctx.stdin(), ctx.stdout(), ctx.stderr())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ctx *testContext) must(exitCode int) *command {
|
|
if exitCode != 0 {
|
|
ctx.t.Fatalf("expected no error, but got exit code %d. Stderr: %s",
|
|
exitCode, ctx.stderrString())
|
|
}
|
|
return ctx.lastCmd
|
|
}
|
|
|
|
func (ctx *testContext) mustFail(exitCode int) string {
|
|
if exitCode == 0 {
|
|
ctx.t.Fatalf("expected an error, but got none")
|
|
}
|
|
return ctx.stderrString()
|
|
}
|
|
|
|
func (ctx *testContext) updateConfig(cfg *config) {
|
|
*ctx.cfg = *cfg
|
|
ctx.cfg.newWarningsDir = filepath.Join(ctx.tempDir, "fatal_clang_warnings")
|
|
ctx.cfg.triciumNitsDir = filepath.Join(ctx.tempDir, "tricium_nits")
|
|
ctx.cfg.crashArtifactsDir = filepath.Join(ctx.tempDir, "clang_crash_diagnostics")
|
|
}
|
|
|
|
func (ctx *testContext) newCommand(path string, args ...string) *command {
|
|
// Create an empty wrapper at the given path.
|
|
// Needed as we are resolving symlinks which stats the wrapper file.
|
|
ctx.writeFile(path, "")
|
|
return &command{
|
|
Path: path,
|
|
Args: args,
|
|
}
|
|
}
|
|
|
|
func (ctx *testContext) writeFile(fullFileName string, fileContent string) {
|
|
if !filepath.IsAbs(fullFileName) {
|
|
fullFileName = filepath.Join(ctx.tempDir, fullFileName)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(fullFileName), 0777); err != nil {
|
|
ctx.t.Fatal(err)
|
|
}
|
|
if err := ioutil.WriteFile(fullFileName, []byte(fileContent), 0777); err != nil {
|
|
ctx.t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (ctx *testContext) symlink(oldname string, newname string) {
|
|
if !filepath.IsAbs(oldname) {
|
|
oldname = filepath.Join(ctx.tempDir, oldname)
|
|
}
|
|
if !filepath.IsAbs(newname) {
|
|
newname = filepath.Join(ctx.tempDir, newname)
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(newname), 0777); err != nil {
|
|
ctx.t.Fatal(err)
|
|
}
|
|
if err := os.Symlink(oldname, newname); err != nil {
|
|
ctx.t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (ctx *testContext) readAllString(r io.Reader) string {
|
|
if r == nil {
|
|
return ""
|
|
}
|
|
bytes, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
ctx.t.Fatal(err)
|
|
}
|
|
return string(bytes)
|
|
}
|
|
|
|
func verifyPath(cmd *command, expectedRegex string) error {
|
|
compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
|
|
if !compiledRegex.MatchString(cmd.Path) {
|
|
return fmt.Errorf("path does not match %s. Actual %s", expectedRegex, cmd.Path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func verifyArgCount(cmd *command, expectedCount int, expectedRegex string) error {
|
|
compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
|
|
count := 0
|
|
for _, arg := range cmd.Args {
|
|
if compiledRegex.MatchString(arg) {
|
|
count++
|
|
}
|
|
}
|
|
if count != expectedCount {
|
|
return fmt.Errorf("expected %d matches for arg %s. All args: %s",
|
|
expectedCount, expectedRegex, cmd.Args)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func verifyArgOrder(cmd *command, expectedRegexes ...string) error {
|
|
compiledRegexes := []*regexp.Regexp{}
|
|
for _, regex := range expectedRegexes {
|
|
compiledRegexes = append(compiledRegexes, regexp.MustCompile(matchFullString(regex)))
|
|
}
|
|
expectedArgIndex := 0
|
|
for _, arg := range cmd.Args {
|
|
if expectedArgIndex == len(compiledRegexes) {
|
|
break
|
|
} else if compiledRegexes[expectedArgIndex].MatchString(arg) {
|
|
expectedArgIndex++
|
|
}
|
|
}
|
|
if expectedArgIndex != len(expectedRegexes) {
|
|
return fmt.Errorf("expected args %s in order. All args: %s",
|
|
expectedRegexes, cmd.Args)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func verifyEnvUpdate(cmd *command, expectedRegex string) error {
|
|
compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
|
|
for _, update := range cmd.EnvUpdates {
|
|
if compiledRegex.MatchString(update) {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("expected at least one match for env update %s. All env updates: %s",
|
|
expectedRegex, cmd.EnvUpdates)
|
|
}
|
|
|
|
func verifyNoEnvUpdate(cmd *command, expectedRegex string) error {
|
|
compiledRegex := regexp.MustCompile(matchFullString(expectedRegex))
|
|
updates := cmd.EnvUpdates
|
|
for _, update := range updates {
|
|
if compiledRegex.MatchString(update) {
|
|
return fmt.Errorf("expected no match for env update %s. All env updates: %s",
|
|
expectedRegex, cmd.EnvUpdates)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func hasInternalError(stderr string) bool {
|
|
return strings.Contains(stderr, "Internal error")
|
|
}
|
|
|
|
func verifyInternalError(stderr string) error {
|
|
if !hasInternalError(stderr) {
|
|
return fmt.Errorf("expected an internal error. Got: %s", stderr)
|
|
}
|
|
if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); !ok {
|
|
return fmt.Errorf("expected a source line reference. Got: %s", stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func verifyNonInternalError(stderr string, expectedRegex string) error {
|
|
if hasInternalError(stderr) {
|
|
return fmt.Errorf("expected a non internal error. Got: %s", stderr)
|
|
}
|
|
if ok, _ := regexp.MatchString(`\w+.go:\d+`, stderr); ok {
|
|
return fmt.Errorf("expected no source line reference. Got: %s", stderr)
|
|
}
|
|
if ok, _ := regexp.MatchString(matchFullString(expectedRegex), strings.TrimSpace(stderr)); !ok {
|
|
return fmt.Errorf("expected stderr matching %s. Got: %s", expectedRegex, stderr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func matchFullString(regex string) string {
|
|
return "^" + regex + "$"
|
|
}
|
|
|
|
func newExitCodeError(exitCode int) error {
|
|
// It's actually hard to create an error that represents a command
|
|
// with exit code. Using a real command instead.
|
|
tmpCmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("exit %d", exitCode))
|
|
return tmpCmd.Run()
|
|
}
|