| // Copyright (c) 2021, Google Inc. |
| // |
| // Permission to use, copy, modify, and/or distribute this software for any |
| // purpose with or without fee is hereby granted, provided that the above |
| // copyright notice and this permission notice appear in all copies. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
| // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
| // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
| // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "compress/bzip2" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "os" |
| "os/exec" |
| "runtime" |
| "strings" |
| "sync" |
| "sync/atomic" |
| ) |
| |
| var ( |
| toolPath *string = flag.String("tool", "", "Path to acvptool binary") |
| moduleWrappers *string = flag.String("module-wrappers", "", "Comma-separated list of name:path pairs for known module wrappers") |
| testsPath *string = flag.String("tests", "", "Path to JSON file listing tests") |
| update *bool = flag.Bool("update", false, "If true then write updated outputs") |
| ) |
| |
| type invocation struct { |
| toolPath string |
| wrapperPath string |
| inPath string |
| expectedPath string |
| } |
| |
| func main() { |
| flag.Parse() |
| |
| if len(*toolPath) == 0 { |
| log.Fatal("-tool must be given") |
| } |
| |
| if len(*moduleWrappers) == 0 { |
| log.Fatal("-module-wrappers must be given") |
| } |
| |
| wrappers := make(map[string]string) |
| pairs := strings.Split(*moduleWrappers, ",") |
| for _, pair := range pairs { |
| parts := strings.SplitN(pair, ":", 2) |
| if _, ok := wrappers[parts[0]]; ok { |
| log.Fatalf("wrapper %q defined twice", parts[0]) |
| } |
| wrappers[parts[0]] = parts[1] |
| } |
| |
| if len(*testsPath) == 0 { |
| log.Fatal("-tests must be given") |
| } |
| |
| testsFile, err := os.Open(*testsPath) |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer testsFile.Close() |
| |
| decoder := json.NewDecoder(testsFile) |
| var tests []struct { |
| Wrapper string |
| In string |
| Out string // Optional, may be empty. |
| } |
| if err := decoder.Decode(&tests); err != nil { |
| log.Fatal(err) |
| } |
| |
| work := make(chan invocation, runtime.NumCPU()) |
| var numFailed uint32 |
| |
| var wg sync.WaitGroup |
| for i := 0; i < runtime.NumCPU(); i++ { |
| wg.Add(1) |
| go worker(&wg, work, &numFailed) |
| } |
| |
| for _, test := range tests { |
| wrapper, ok := wrappers[test.Wrapper] |
| if !ok { |
| log.Fatalf("wrapper %q not specified on command line", test.Wrapper) |
| } |
| work <- invocation{ |
| toolPath: *toolPath, |
| wrapperPath: wrapper, |
| inPath: test.In, |
| expectedPath: test.Out, |
| } |
| } |
| |
| close(work) |
| wg.Wait() |
| |
| n := atomic.LoadUint32(&numFailed) |
| if n > 0 { |
| log.Printf("Failed %d tests", n) |
| os.Exit(1) |
| } else { |
| log.Printf("%d ACVP tests matched expectations", len(tests)) |
| } |
| } |
| |
| func worker(wg *sync.WaitGroup, work <-chan invocation, numFailed *uint32) { |
| defer wg.Done() |
| |
| for test := range work { |
| if err := doTest(test); err != nil { |
| log.Printf("Test failed for %q: %s", test.inPath, err) |
| atomic.AddUint32(numFailed, 1) |
| } |
| } |
| } |
| |
| func doTest(test invocation) error { |
| input, err := os.Open(test.inPath) |
| if err != nil { |
| return fmt.Errorf("Failed to open %q: %s", test.inPath, err) |
| } |
| defer input.Close() |
| |
| tempFile, err := ioutil.TempFile("", "boringssl-check_expected-") |
| if err != nil { |
| return fmt.Errorf("Failed to create temp file: %s", err) |
| } |
| defer os.Remove(tempFile.Name()) |
| defer tempFile.Close() |
| |
| decompressor := bzip2.NewReader(input) |
| if _, err := io.Copy(tempFile, decompressor); err != nil { |
| return fmt.Errorf("Failed to decompress %q: %s", test.inPath, err) |
| } |
| |
| cmd := exec.Command(test.toolPath, "-wrapper", test.wrapperPath, "-json", tempFile.Name()) |
| result, err := cmd.CombinedOutput() |
| if err != nil { |
| os.Stderr.Write(result) |
| return fmt.Errorf("Failed to process %q", test.inPath) |
| } |
| |
| if len(test.expectedPath) == 0 { |
| // This test has variable output and thus cannot be compared against a fixed |
| // result. |
| return nil |
| } |
| |
| expected, err := os.Open(test.expectedPath) |
| if err != nil { |
| if *update { |
| writeUpdate(test.expectedPath, result) |
| } |
| return fmt.Errorf("Failed to open %q: %s", test.expectedPath, err) |
| } |
| defer expected.Close() |
| |
| decompressor = bzip2.NewReader(expected) |
| |
| var expectedBuf bytes.Buffer |
| if _, err := io.Copy(&expectedBuf, decompressor); err != nil { |
| return fmt.Errorf("Failed to decompress %q: %s", test.expectedPath, err) |
| } |
| |
| if !bytes.Equal(expectedBuf.Bytes(), result) { |
| if *update { |
| writeUpdate(test.expectedPath, result) |
| } |
| return fmt.Errorf("Mismatch for %q", test.expectedPath) |
| } |
| |
| return nil |
| } |
| |
| func writeUpdate(path string, contents []byte) { |
| if err := ioutil.WriteFile(path, contents, 0644); err != nil { |
| log.Printf("Failed to create missing file %q: %s", path, err) |
| } else { |
| log.Printf("Wrote %q", path) |
| } |
| } |