blob: 4f5b3dc050699fab886ec59185a6119743bf5b78 [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 (
"bytes"
"fmt"
"io"
"path/filepath"
"strings"
"testing"
"text/template"
)
func TestCompareToOldPythonWrapperCompilerCommand(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
pathSuffix := ""
extraArgs := []string{}
exitCode := 0
newWrapperExitCode := 0
reset := func() {
ctx.stderrBuffer.Reset()
pathSuffix = ""
extraArgs = []string{}
exitCode = 0
newWrapperExitCode = 0
}
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
writePythonMockWrapper(ctx, &mockWrapperConfig{
Cmds: []*mockWrapperCmd{
{
Path: cmd.Path + pathSuffix,
Args: append(cmd.Args, extraArgs...),
ExitCode: exitCode,
},
},
})
if newWrapperExitCode != 0 {
return newExitCodeError(newWrapperExitCode)
}
return nil
}
// Note: This will cause only the compiler command.
inputCmd := ctx.newCommand(gccX86_64)
reset()
pathSuffix = "xyz"
stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "Index 0: path") {
t.Errorf("expected path difference error. Got: %s", stderr)
}
reset()
extraArgs = []string{"xyz"}
stderr = ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "Index 0: args") {
t.Errorf("expected args difference error. Got: %s", stderr)
}
reset()
exitCode = 1
stderr = ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "exit codes differ: old 1, new 0") {
t.Errorf("expected exit code difference error. Got: %s", stderr)
}
reset()
newWrapperExitCode = 1
stderr = ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "exit codes differ: old 0, new 1") {
t.Errorf("expected exit code difference error. Got: %s", stderr)
}
reset()
ctx.must(callCompiler(ctx, ctx.cfg, inputCmd))
})
}
func TestCompareToOldPythonWrapperNestedCommand(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
pathSuffix := ""
extraArgs := []string{}
wrapperCfg := &mockWrapperConfig{}
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
isNestedCmd := len(wrapperCfg.Cmds) == 0
var wrapperCmd *mockWrapperCmd
if isNestedCmd {
wrapperCmd = &mockWrapperCmd{
Path: cmd.Path + pathSuffix,
Args: append(cmd.Args, extraArgs...),
}
} else {
wrapperCmd = &mockWrapperCmd{
Path: cmd.Path,
Args: cmd.Args,
}
}
wrapperCfg.Cmds = append(wrapperCfg.Cmds, wrapperCmd)
if !isNestedCmd {
writePythonMockWrapper(ctx, wrapperCfg)
}
return nil
}
// Note: This will cause a nested command call.
inputCmd := ctx.newCommand(clangX86_64, "-Xclang-path=somedir", mainCc)
ctx.stderrBuffer.Reset()
wrapperCfg = &mockWrapperConfig{}
pathSuffix = "xyz"
extraArgs = nil
stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "Index 0: path") {
t.Errorf("expected path difference error. Got: %s", stderr)
}
if !strings.Contains(stderr, "Index 1: none") {
t.Errorf("expected no difference for cmd index 1. Got: %s", stderr)
}
ctx.stderrBuffer.Reset()
wrapperCfg = &mockWrapperConfig{}
pathSuffix = ""
extraArgs = []string{"xyz"}
stderr = ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "Index 0: args") {
t.Errorf("expected args difference error. Got: %s", stderr)
}
if !strings.Contains(stderr, "Index 1: none") {
t.Errorf("expected no difference for cmd index 1. Got: %s", stderr)
}
wrapperCfg = &mockWrapperConfig{}
pathSuffix = ""
extraArgs = nil
ctx.must(callCompiler(ctx, ctx.cfg, inputCmd))
})
}
func TestCompareToOldShellWrapperCompilerCommand(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
pathSuffix := ""
extraArgs := []string{}
exitCode := 0
newWrapperExitCode := 0
reset := func() {
ctx.stderrBuffer.Reset()
pathSuffix = ""
extraArgs = []string{}
exitCode = 0
newWrapperExitCode = 0
}
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
writeShellMockWrapper(ctx, &mockWrapperConfig{
Cmds: []*mockWrapperCmd{
{
Path: cmd.Path + pathSuffix,
Args: append(cmd.Args, extraArgs...),
ExitCode: exitCode,
},
},
})
if newWrapperExitCode != 0 {
return newExitCodeError(newWrapperExitCode)
}
return nil
}
// Note: This will cause only the compiler command.
inputCmd := ctx.newCommand(gccX86_64)
reset()
pathSuffix = "xyz"
stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "Index 0: path") {
t.Errorf("expected path difference error. Got: %s", stderr)
}
reset()
extraArgs = []string{"xyz"}
stderr = ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "Index 0: args") {
t.Errorf("expected args difference error. Got: %s", stderr)
}
reset()
exitCode = 1
stderr = ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "exit codes differ: old 1, new 0") {
t.Errorf("expected exit code difference error. Got: %s", stderr)
}
reset()
newWrapperExitCode = 1
stderr = ctx.mustFail(callCompiler(ctx, ctx.cfg, inputCmd))
if !strings.Contains(stderr, "exit codes differ: old 0, new 1") {
t.Errorf("expected exit code difference error. Got: %s", stderr)
}
reset()
ctx.must(callCompiler(ctx, ctx.cfg, inputCmd))
reset()
ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, " spaces ")))
})
}
func TestCompareToOldWrapperEscapeStdoutAndStderr(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
io.WriteString(stdout, "a\n'b'\\")
io.WriteString(stderr, "c\n'd'\\")
writePythonMockWrapper(ctx, &mockWrapperConfig{
Cmds: []*mockWrapperCmd{
{
Path: cmd.Path,
Args: cmd.Args,
},
},
})
return nil
}
ctx.must(callCompiler(ctx, ctx.cfg,
ctx.newCommand(clangX86_64, mainCc)))
if ctx.stdoutString() != "a\n'b'\\" {
t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString())
}
if ctx.stderrString() != "c\n'd'\\" {
t.Errorf("unexpected stderr. Got: %s", ctx.stderrString())
}
})
}
func TestCompareToOldWrapperSupportUtf8InStdoutAndStderr(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
io.WriteString(stdout, "©")
io.WriteString(stderr, "®")
writePythonMockWrapper(ctx, &mockWrapperConfig{
Cmds: []*mockWrapperCmd{
{
Path: cmd.Path,
Args: cmd.Args,
},
},
})
return nil
}
ctx.must(callCompiler(ctx, ctx.cfg,
ctx.newCommand(clangX86_64, mainCc)))
if ctx.stdoutString() != "©" {
t.Errorf("unexpected stdout. Got: %s", ctx.stdoutString())
}
if ctx.stderrString() != "®" {
t.Errorf("unexpected stderr. Got: %s", ctx.stderrString())
}
})
}
func TestCompareToOldPythonWrapperArgumentsWithSpaces(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
writePythonMockWrapper(ctx, &mockWrapperConfig{
Cmds: []*mockWrapperCmd{
{
Path: cmd.Path,
Args: cmd.Args,
},
},
})
return nil
}
ctx.must(callCompiler(ctx, ctx.cfg,
ctx.newCommand(clangX86_64, "a b", "c", mainCc)))
})
}
func TestCompareToOldShellWrapperArgumentsWithSpaces(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
writeShellMockWrapper(ctx, &mockWrapperConfig{
Cmds: []*mockWrapperCmd{
{
Path: cmd.Path,
Args: cmd.Args,
},
},
})
return nil
}
ctx.must(callCompiler(ctx, ctx.cfg,
ctx.newCommand(clangX86_64, "a b", "c", mainCc)))
})
}
func TestForwardStdinWhenUsingOldWrapper(t *testing.T) {
withTestContext(t, func(ctx *testContext) {
io.WriteString(&ctx.stdinBuffer, "someinput")
ctx.cfg.mockOldWrapperCmds = false
ctx.cfg.oldWrapperPath = filepath.Join(ctx.tempDir, "fakewrapper")
ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
writeShellMockWrapper(ctx, &mockWrapperConfig{
Cmds: []*mockWrapperCmd{
{
Path: cmd.Path,
Args: cmd.Args,
},
},
})
stdinStr := ctx.readAllString(stdin)
if stdinStr != "someinput" {
return fmt.Errorf("unexpected stdin. Got: %s", stdinStr)
}
return nil
}
ctx.must(callCompiler(ctx, ctx.cfg,
ctx.newCommand(clangX86_64, "-", mainCc)))
})
}
func writePythonMockWrapper(ctx *testContext, cfg *mockWrapperConfig) {
const mockTemplate = `
from __future__ import print_function
import os
import sys
import subprocess
mockCmds = [{{range .Cmds}} {
'path': '{{.Path}}',
'args': [{{range .Args}}'{{.}}',{{end}}],
'exitcode': {{.ExitCode}},
},{{end}}]
def execv_impl(binary, args):
cmd = mockCmds.pop(0)
sys.exit(cmd['exitcode'])
os.execv = execv_impl
def check_output_impl(args):
cmd = mockCmds.pop(0)
if cmd['exitcode']:
raise subprocess.CalledProcessError(cmd['exitcode'])
return ""
subprocess.check_output = check_output_impl
def main():
while len(mockCmds) > 1:
subprocess.check_output([mockCmds[0]['path']] + mockCmds[0]['args'])
os.execv(mockCmds[0]['path'], [mockCmds[0]['path']] + mockCmds[0]['args'])
if __name__ == '__main__':
sys.exit(main())
`
tmpl, err := template.New("mock").Parse(mockTemplate)
if err != nil {
ctx.t.Fatalf("failed to parse old wrapper template. Error: %s", err)
}
buf := bytes.Buffer{}
if err := tmpl.Execute(&buf, cfg); err != nil {
ctx.t.Fatalf("failed to execute the template. Error: %s", err)
}
ctx.writeFile(ctx.cfg.oldWrapperPath, buf.String())
}
func writeShellMockWrapper(ctx *testContext, cfg *mockWrapperConfig) {
const mockTemplate = `#!/bin/sh
EXEC=fake_exec
function fake_exec {
exit {{(index .Cmds 0).ExitCode}}
}
$EXEC "{{(index .Cmds 0).Path}}"{{range (index .Cmds 0).Args}} "{{.}}"{{end}}
`
tmpl, err := template.New("mock").Parse(mockTemplate)
if err != nil {
ctx.t.Fatalf("failed to parse old wrapper template. Error: %s", err)
}
buf := bytes.Buffer{}
if err := tmpl.Execute(&buf, cfg); err != nil {
ctx.t.Fatalf("failed to execute the template. Error: %s", err)
}
ctx.writeFile(ctx.cfg.oldWrapperPath, buf.String())
}
// Note: Fields have to be uppercase so that they can be used with template.
type mockWrapperConfig struct {
Cmds []*mockWrapperCmd
}
// Note: Fields have to be uppercase so that they can be used with template.
type mockWrapperCmd struct {
Path string
Args []string
ExitCode int
}