| // 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" |
| "encoding/json" |
| "flag" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strings" |
| ) |
| |
| var updateGoldenFiles = flag.Bool("updategolden", false, "update golden files") |
| var filterGoldenTests = flag.String("rungolden", "", "regex filter for golden tests to run") |
| |
| type goldenRecordSection struct { |
| Name string `json:"name"` |
| ignoreOldWrapper bool |
| Records []goldenRecord `json:"records"` |
| } |
| |
| type goldenRecord struct { |
| Wd string `json:"wd"` |
| Env []string `json:"env,omitempty"` |
| // runGoldenRecords will read cmd and fill |
| // stdout, stderr, exitCode. |
| WrapperCmd commandResult `json:"wrapper"` |
| // runGoldenRecords will read stdout, stderr, err |
| // and fill cmd |
| Cmds []commandResult `json:"cmds"` |
| } |
| |
| func newGoldenCmd(path string, args ...string) commandResult { |
| return commandResult{ |
| Cmd: &command{ |
| Path: path, |
| Args: args, |
| }, |
| } |
| } |
| |
| var okResult = commandResult{} |
| var okResults = []commandResult{okResult} |
| var errorResult = commandResult{ |
| ExitCode: 1, |
| Stderr: "someerror", |
| Stdout: "somemessage", |
| } |
| var errorResults = []commandResult{errorResult} |
| |
| func runGoldenRecords(ctx *testContext, goldenFile string, sections []goldenRecordSection) { |
| if filterPattern := *filterGoldenTests; filterPattern != "" { |
| sections = filterGoldenRecords(filterPattern, sections) |
| } |
| if len(sections) == 0 { |
| ctx.t.Errorf("No goldenrecords given.") |
| return |
| } |
| sections = fillGoldenResults(ctx, sections) |
| if *updateGoldenFiles { |
| log.Printf("updating golden file under %s", goldenFile) |
| if err := os.MkdirAll(filepath.Dir(goldenFile), 0777); err != nil { |
| ctx.t.Fatal(err) |
| } |
| goldenFile, err := os.Create(goldenFile) |
| if err != nil { |
| ctx.t.Fatal(err) |
| } |
| defer goldenFile.Close() |
| |
| writeGoldenRecords(ctx, goldenFile, sections) |
| } else { |
| compareBuffer := &bytes.Buffer{} |
| writeGoldenRecords(ctx, compareBuffer, sections) |
| goldenFileData, err := ioutil.ReadFile(goldenFile) |
| if err != nil { |
| ctx.t.Fatal(err) |
| } |
| if !bytes.Equal(compareBuffer.Bytes(), goldenFileData) { |
| ctx.t.Fatalf("Commands don't match the golden file under %s. Please regenerate via -updategolden to check the differences.", |
| goldenFile) |
| } |
| } |
| } |
| |
| func filterGoldenRecords(pattern string, sections []goldenRecordSection) []goldenRecordSection { |
| matcher := regexp.MustCompile(pattern) |
| newSections := []goldenRecordSection{} |
| for _, section := range sections { |
| newRecords := []goldenRecord{} |
| for _, record := range section.Records { |
| cmd := record.WrapperCmd.Cmd |
| str := strings.Join(append(append(record.Env, cmd.Path), cmd.Args...), " ") |
| if matcher.MatchString(str) { |
| newRecords = append(newRecords, record) |
| } |
| } |
| section.Records = newRecords |
| newSections = append(newSections, section) |
| } |
| return newSections |
| } |
| |
| func fillGoldenResults(ctx *testContext, sections []goldenRecordSection) []goldenRecordSection { |
| oldWrapperPath := ctx.cfg.oldWrapperPath |
| newSections := []goldenRecordSection{} |
| for _, section := range sections { |
| ctx.cfg.oldWrapperPath = oldWrapperPath |
| if section.ignoreOldWrapper { |
| ctx.cfg.oldWrapperPath = "" |
| } |
| |
| newRecords := []goldenRecord{} |
| for _, record := range section.Records { |
| newCmds := []commandResult{} |
| ctx.cmdMock = func(cmd *command, stdout io.Writer, stderr io.Writer) error { |
| if len(newCmds) >= len(record.Cmds) { |
| ctx.t.Errorf("Not enough commands specified for wrapperCmd %#v and env %#v. Expected: %#v", |
| record.WrapperCmd.Cmd, record.Env, record.Cmds) |
| return nil |
| } |
| cmdResult := record.Cmds[len(newCmds)] |
| cmdResult.Cmd = cmd |
| newCmds = append(newCmds, cmdResult) |
| io.WriteString(stdout, cmdResult.Stdout) |
| io.WriteString(stderr, cmdResult.Stderr) |
| if cmdResult.ExitCode != 0 { |
| return newExitCodeError(cmdResult.ExitCode) |
| } |
| return nil |
| } |
| ctx.stdoutBuffer.Reset() |
| ctx.stderrBuffer.Reset() |
| ctx.env = record.Env |
| if record.Wd == "" { |
| record.Wd = ctx.tempDir |
| } |
| ctx.wd = record.Wd |
| // Create an empty wrapper at the given path. |
| // Needed as we are resolving symlinks which stats the wrapper file. |
| ctx.writeFile(record.WrapperCmd.Cmd.Path, "") |
| record.WrapperCmd.ExitCode = callCompiler(ctx, ctx.cfg, record.WrapperCmd.Cmd) |
| if hasInternalError(ctx.stderrString()) { |
| ctx.t.Errorf("found an internal error for wrapperCmd %#v and env #%v. Got: %s", |
| record.WrapperCmd.Cmd, record.Env, ctx.stderrString()) |
| } |
| if len(newCmds) < len(record.Cmds) { |
| ctx.t.Errorf("Too many commands specified for wrapperCmd %#v and env %#v. Expected: %#v", |
| record.WrapperCmd.Cmd, record.Env, record.Cmds) |
| } |
| record.Cmds = newCmds |
| record.WrapperCmd.Stdout = ctx.stdoutString() |
| record.WrapperCmd.Stderr = ctx.stderrString() |
| newRecords = append(newRecords, record) |
| } |
| section.Records = newRecords |
| newSections = append(newSections, section) |
| } |
| return newSections |
| } |
| |
| func writeGoldenRecords(ctx *testContext, writer io.Writer, sections []goldenRecordSection) { |
| // Replace the temp dir with a stable path so that the goldens stay stable. |
| stableTempDir := filepath.Join(filepath.Dir(ctx.tempDir), "stable") |
| writer = &replacingWriter{ |
| Writer: writer, |
| old: []byte(ctx.tempDir), |
| new: []byte(stableTempDir), |
| } |
| enc := json.NewEncoder(writer) |
| enc.SetIndent("", " ") |
| if err := enc.Encode(sections); err != nil { |
| ctx.t.Fatal(err) |
| } |
| } |
| |
| type replacingWriter struct { |
| io.Writer |
| old []byte |
| new []byte |
| } |
| |
| func (writer *replacingWriter) Write(p []byte) (n int, err error) { |
| p = bytes.ReplaceAll(p, writer.old, writer.new) |
| return writer.Writer.Write(p) |
| } |