// 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 }