// 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 ( "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" "regexp" "strconv" "strings" "testing" ) func TestForwardStdOutAndStdErrAndExitCodeFromLogRusage(t *testing.T) { withLogRusageTestContext(t, func(ctx *testContext) { ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { fmt.Fprint(stdout, "somemessage") fmt.Fprint(stderr, "someerror") return newExitCodeError(23) } exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)) if exitCode != 23 { t.Errorf("unexpected exit code. Got: %d", exitCode) } if ctx.stdoutString() != "somemessage" { t.Errorf("stdout was not forwarded. Got: %s", ctx.stdoutString()) } if ctx.stderrString() != "someerror" { t.Errorf("stderr was not forwarded. Got: %s", ctx.stderrString()) } }) } func TestForwardStdinFromLogRusage(t *testing.T) { withLogRusageTestContext(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) } return nil } io.WriteString(&ctx.stdinBuffer, "someinput") ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(clangX86_64, "-", mainCc))) }) } func TestReportGeneralErrorsFromLogRusage(t *testing.T) { withLogRusageTestContext(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(gccX86_64, mainCc))) if err := verifyInternalError(stderr); err != nil { t.Fatal(err) } if !strings.Contains(stderr, "someerror") { t.Errorf("unexpected error. Got: %s", stderr) } }) } func TestCreateDirAndFileForLogRusage(t *testing.T) { withLogRusageTestContext(t, func(ctx *testContext) { logFileName := filepath.Join(ctx.tempDir, "somedir", "rusage.log") ctx.env = []string{"GETRUSAGE=" + logFileName} ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) if _, err := os.Stat(logFileName); err != nil { t.Errorf("rusage log file does not exist: %s", err) } }) } func TestLogRusageFileContent(t *testing.T) { withLogRusageTestContext(t, func(ctx *testContext) { logFileName := filepath.Join(ctx.tempDir, "rusage.log") ctx.env = []string{"GETRUSAGE=" + logFileName} ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) data, err := ioutil.ReadFile(logFileName) if err != nil { t.Errorf("could not read the rusage log file. Error: %s", err) } // Example output: // 0.100318 : 0.103412 : 0.096386 : 6508 : /tmp/compiler_wrapper036306868/x86_64-cros-linux-gnu-gcc.real : x86_64-cros-linux-gnu-gcc.real --sysroot=/tmp/compiler_wrapper036306868/usr/x86_64-cros-linux-gnu main.cc -mno-movbe logParts := strings.Split(string(data), " : ") if len(logParts) != 6 { t.Errorf("unexpected number of rusage log parts. Got: %s", logParts) } // First 3 numbers are times in seconds. for i := 0; i < 3; i++ { if _, err := strconv.ParseFloat(logParts[i], 64); err != nil { t.Errorf("unexpected value for index %d. Got: %s", i, logParts[i]) } } // Then an int for the memory usage if _, err := strconv.ParseInt(logParts[3], 10, 64); err != nil { t.Errorf("unexpected mem usage. Got: %s", logParts[3]) } // Then the full path of the compiler if logParts[4] != filepath.Join(ctx.tempDir, gccX86_64+".real") { t.Errorf("unexpected compiler path. Got: %s", logParts[4]) } // Then the arguments, prefixes with the compiler basename if matched, _ := regexp.MatchString("x86_64-cros-linux-gnu-gcc.real --sysroot=.* main.cc", logParts[5]); !matched { t.Errorf("unexpected compiler args. Got: %s", logParts[5]) } }) } func TestLogRusageAppendsToFile(t *testing.T) { withLogRusageTestContext(t, func(ctx *testContext) { logFileName := filepath.Join(ctx.tempDir, "rusage.log") ctx.env = []string{"GETRUSAGE=" + logFileName} ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) data, err := ioutil.ReadFile(logFileName) if err != nil { t.Errorf("could not read the rusage log file. Error: %s", err) } firstCallLines := strings.Split(string(data), "\n") if len(firstCallLines) != 2 { t.Errorf("unexpected number of lines. Got: %s", firstCallLines) } if firstCallLines[0] == "" { t.Error("first line was empty") } if firstCallLines[1] != "" { t.Errorf("second line was not empty. Got: %s", firstCallLines[1]) } ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) data, err = ioutil.ReadFile(logFileName) if err != nil { t.Errorf("could not read the rusage log file. Error: %s", err) } secondCallLines := strings.Split(string(data), "\n") if len(secondCallLines) != 3 { t.Errorf("unexpected number of lines. Got: %s", secondCallLines) } if secondCallLines[0] != firstCallLines[0] { t.Errorf("first line was changed. Got: %s", secondCallLines[0]) } if secondCallLines[1] == "" { t.Error("second line was empty") } if secondCallLines[2] != "" { t.Errorf("third line was not empty. Got: %s", secondCallLines[2]) } }) } func withLogRusageTestContext(t *testing.T, work func(ctx *testContext)) { withTestContext(t, func(ctx *testContext) { ctx.env = []string{"GETRUSAGE=" + filepath.Join(ctx.tempDir, "rusage.log")} work(ctx) }) }