• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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