1// Copyright 2021 The BoringSSL Authors 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15//go:build ignore 16 17package main 18 19import ( 20 "bytes" 21 "compress/bzip2" 22 "encoding/json" 23 "flag" 24 "fmt" 25 "io" 26 "log" 27 "os" 28 "os/exec" 29 "runtime" 30 "strings" 31 "sync" 32 "sync/atomic" 33) 34 35var ( 36 toolPath *string = flag.String("tool", "", "Path to acvptool binary") 37 moduleWrappers *string = flag.String("module-wrappers", "", "Comma-separated list of name:path pairs for known module wrappers") 38 testsPath *string = flag.String("tests", "", "Path to JSON file listing tests") 39 update *bool = flag.Bool("update", false, "If true then write updated outputs") 40) 41 42type invocation struct { 43 toolPath string 44 wrapperPath string 45 inPath string 46 expectedPath string 47 configPath string 48} 49 50func main() { 51 flag.Parse() 52 53 if len(*toolPath) == 0 { 54 log.Fatal("-tool must be given") 55 } 56 57 if len(*moduleWrappers) == 0 { 58 log.Fatal("-module-wrappers must be given") 59 } 60 61 wrappers := make(map[string]string) 62 pairs := strings.Split(*moduleWrappers, ",") 63 for _, pair := range pairs { 64 parts := strings.SplitN(pair, ":", 2) 65 if _, ok := wrappers[parts[0]]; ok { 66 log.Fatalf("wrapper %q defined twice", parts[0]) 67 } 68 wrappers[parts[0]] = parts[1] 69 } 70 71 if len(*testsPath) == 0 { 72 log.Fatal("-tests must be given") 73 } 74 75 testsFile, err := os.Open(*testsPath) 76 if err != nil { 77 log.Fatal(err) 78 } 79 defer testsFile.Close() 80 81 decoder := json.NewDecoder(testsFile) 82 var tests []struct { 83 Wrapper string 84 In string 85 Out string // Optional, may be empty. 86 } 87 if err := decoder.Decode(&tests); err != nil { 88 log.Fatal(err) 89 } 90 91 configFile, err := os.CreateTemp("", "boringssl-check_expected-config-") 92 if err != nil { 93 log.Fatalf("Failed to create temp file for config: %s", err) 94 } 95 defer os.Remove(configFile.Name()) 96 if _, err := configFile.WriteString("{}\n"); err != nil { 97 log.Fatalf("Failed to write config file: %s", err) 98 } 99 100 work := make(chan invocation, runtime.NumCPU()) 101 var numFailed uint32 102 103 var wg sync.WaitGroup 104 for i := 0; i < runtime.NumCPU(); i++ { 105 wg.Add(1) 106 go worker(&wg, work, &numFailed) 107 } 108 109 for _, test := range tests { 110 wrapper, ok := wrappers[test.Wrapper] 111 if !ok { 112 log.Fatalf("wrapper %q not specified on command line", test.Wrapper) 113 } 114 work <- invocation{ 115 toolPath: *toolPath, 116 wrapperPath: wrapper, 117 inPath: test.In, 118 expectedPath: test.Out, 119 configPath: configFile.Name(), 120 } 121 } 122 123 close(work) 124 wg.Wait() 125 126 n := atomic.LoadUint32(&numFailed) 127 if n > 0 { 128 log.Printf("Failed %d tests", n) 129 os.Exit(1) 130 } else { 131 log.Printf("%d ACVP tests matched expectations", len(tests)) 132 } 133} 134 135func worker(wg *sync.WaitGroup, work <-chan invocation, numFailed *uint32) { 136 defer wg.Done() 137 138 for test := range work { 139 if err := doTest(test); err != nil { 140 log.Printf("Test failed for %q: %s", test.inPath, err) 141 atomic.AddUint32(numFailed, 1) 142 } 143 } 144} 145 146func doTest(test invocation) error { 147 input, err := os.Open(test.inPath) 148 if err != nil { 149 return fmt.Errorf("Failed to open %q: %s", test.inPath, err) 150 } 151 defer input.Close() 152 153 tempFile, err := os.CreateTemp("", "boringssl-check_expected-") 154 if err != nil { 155 return fmt.Errorf("Failed to create temp file: %s", err) 156 } 157 defer os.Remove(tempFile.Name()) 158 defer tempFile.Close() 159 160 decompressor := bzip2.NewReader(input) 161 if _, err := io.Copy(tempFile, decompressor); err != nil { 162 return fmt.Errorf("Failed to decompress %q: %s", test.inPath, err) 163 } 164 165 cmd := exec.Command(test.toolPath, "-wrapper", test.wrapperPath, "-json", tempFile.Name(), "-config", test.configPath) 166 result, err := cmd.CombinedOutput() 167 if err != nil { 168 os.Stderr.Write(result) 169 return fmt.Errorf("Failed to process %q", test.inPath) 170 } 171 172 if len(test.expectedPath) == 0 { 173 // This test has variable output and thus cannot be compared against a fixed 174 // result. 175 return nil 176 } 177 178 expected, err := os.Open(test.expectedPath) 179 if err != nil { 180 if *update { 181 writeUpdate(test.expectedPath, result) 182 } 183 return fmt.Errorf("Failed to open %q: %s", test.expectedPath, err) 184 } 185 defer expected.Close() 186 187 decompressor = bzip2.NewReader(expected) 188 189 var expectedBuf bytes.Buffer 190 if _, err := io.Copy(&expectedBuf, decompressor); err != nil { 191 return fmt.Errorf("Failed to decompress %q: %s", test.expectedPath, err) 192 } 193 194 if !bytes.Equal(expectedBuf.Bytes(), result) { 195 if *update { 196 writeUpdate(test.expectedPath, result) 197 } 198 return fmt.Errorf("Mismatch for %q", test.expectedPath) 199 } 200 201 return nil 202} 203 204func writeUpdate(path string, contents []byte) { 205 path = strings.TrimSuffix(path, ".bz2") 206 if err := os.WriteFile(path, contents, 0644); err != nil { 207 log.Printf("Failed to create missing file %q: %s", path, err) 208 } else { 209 log.Printf("Wrote %q", path) 210 } 211} 212