|  | // Copyright 2021 The BoringSSL Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | //go:build ignore | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "compress/bzip2" | 
|  | "encoding/json" | 
|  | "flag" | 
|  | "fmt" | 
|  | "io" | 
|  | "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 | 
|  | configPath   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) | 
|  | } | 
|  |  | 
|  | configFile, err := os.CreateTemp("", "boringssl-check_expected-config-") | 
|  | if err != nil { | 
|  | log.Fatalf("Failed to create temp file for config: %s", err) | 
|  | } | 
|  | defer os.Remove(configFile.Name()) | 
|  | if _, err := configFile.WriteString("{}\n"); err != nil { | 
|  | log.Fatalf("Failed to write config file: %s", 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, | 
|  | configPath:   configFile.Name(), | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 := os.CreateTemp("", "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(), "-config", test.configPath) | 
|  | 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) { | 
|  | path = strings.TrimSuffix(path, ".bz2") | 
|  | if err := os.WriteFile(path, contents, 0644); err != nil { | 
|  | log.Printf("Failed to create missing file %q: %s", path, err) | 
|  | } else { | 
|  | log.Printf("Wrote %q", path) | 
|  | } | 
|  | } |