@@ -72,57 +72,124 @@ export async function testEncryptVector ({ name, keysInfo, encryptOp, plainTextD
72
72
}
73
73
}
74
74
75
- export async function integrationDecryptTestVectors ( vectorFile : string , tolerateFailures : number = 0 , testName ?: string ) {
75
+ export async function integrationDecryptTestVectors ( vectorFile : string , tolerateFailures : number = 0 , testName ?: string , concurrency : number = 1 ) {
76
76
const tests = await getDecryptTestVectorIterator ( vectorFile )
77
- let failureCount = 0
78
- for ( const test of tests ) {
77
+
78
+ return parallelTests ( concurrency , tolerateFailures , runTest , tests )
79
+
80
+ async function runTest ( test : TestVectorInfo ) : Promise < boolean > {
79
81
if ( testName ) {
80
- if ( test . name !== testName ) continue
82
+ if ( test . name !== testName ) return true
81
83
}
82
84
const { result, name, err } = await testDecryptVector ( test )
83
85
if ( result ) {
84
86
console . log ( { name, result } )
87
+ return true
85
88
} else {
86
89
if ( err && notSupportedDecryptMessages . includes ( err . message ) ) {
87
90
console . log ( { name, result : `Not supported: ${ err . message } ` } )
88
- continue
91
+ return true
89
92
}
90
93
console . log ( { name, result, err } )
91
- }
92
- if ( ! result ) {
93
- failureCount += 1
94
- if ( ! tolerateFailures ) return failureCount
95
- tolerateFailures --
94
+ return false
96
95
}
97
96
}
98
- return failureCount
99
97
}
100
98
101
- export async function integrationEncryptTestVectors ( manifestFile : string , keyFile : string , decryptOracle : string , tolerateFailures : number = 0 , testName ?: string ) {
99
+ export async function integrationEncryptTestVectors ( manifestFile : string , keyFile : string , decryptOracle : string , tolerateFailures : number = 0 , testName ?: string , concurrency : number = 1 ) {
102
100
const decryptOracleUrl = new URL ( decryptOracle )
103
101
const tests = await getEncryptTestVectorIterator ( manifestFile , keyFile )
104
- let failureCount = 0
105
- for ( const test of tests ) {
102
+
103
+ return parallelTests ( concurrency , tolerateFailures , runTest , tests )
104
+
105
+ async function runTest ( test : EncryptTestVectorInfo ) : Promise < boolean > {
106
106
if ( testName ) {
107
- if ( test . name !== testName ) continue
107
+ if ( test . name !== testName ) return true
108
108
}
109
109
const { result, name, err } = await testEncryptVector ( test , decryptOracleUrl )
110
110
if ( result ) {
111
111
console . log ( { name, result } )
112
+ return true
112
113
} else {
113
114
if ( err && notSupportedEncryptMessages . includes ( err . message ) ) {
114
115
console . log ( { name, result : `Not supported: ${ err . message } ` } )
115
- continue
116
+ return true
116
117
}
117
118
console . log ( { name, result, err } )
119
+ return false
118
120
}
119
- if ( ! result ) {
120
- failureCount += 1
121
- if ( ! tolerateFailures ) return failureCount
122
- tolerateFailures --
123
- }
124
121
}
125
- return failureCount
122
+ }
123
+
124
+ async function parallelTests <
125
+ Test extends EncryptTestVectorInfo | TestVectorInfo ,
126
+ work extends ( test : Test ) => Promise < boolean >
127
+ > ( max : number , tolerateFailures : number , runTest : work , tests : IterableIterator < Test > ) {
128
+ let _resolve : ( failureCount : number ) => void
129
+ const queue = new Set < Promise < void > > ( )
130
+ let failureCount = 0
131
+
132
+ return new Promise < number > ( ( resolve ) => {
133
+ _resolve = resolve
134
+ enqueue ( )
135
+ } )
136
+
137
+ function enqueue ( ) : void {
138
+ /* If there are more failures than I am willing to tolerate, stop. */
139
+ if ( failureCount > tolerateFailures ) return _resolve ( failureCount )
140
+ /* Do not over-fill the queue! */
141
+ if ( queue . size > max ) return
142
+
143
+ const { value, done } = tests . next ( )
144
+ /* There is an edge here,
145
+ * you _could_ return a value *and* be done.
146
+ * Most iterators don't but in this case
147
+ * we just process the value and ask for another.
148
+ * Which will return done as true again.
149
+ */
150
+ if ( ! value && done ) return _resolve ( failureCount )
151
+
152
+ /* I need to define the work to be enqueue
153
+ * and a way to dequeue this work when complete.
154
+ * A Set of promises works nicely.
155
+ * Hold the variable here
156
+ * put it in the Set, take it out, and Bob's your uncle.
157
+ */
158
+ const work : Promise < void > = runTest ( value )
159
+ . then ( ( pass : boolean ) => {
160
+ if ( ! pass ) failureCount += 1
161
+ } )
162
+ /* If there is some unknown error,
163
+ * it's just an error...
164
+ * Treat it like a test failure.
165
+ */
166
+ . catch ( ( err ) => {
167
+ console . log ( err )
168
+ failureCount += 1
169
+ } )
170
+ . then ( ( ) => {
171
+ /* Dequeue this work. */
172
+ queue . delete ( work )
173
+ /* More to eat? */
174
+ enqueue ( )
175
+ } )
176
+
177
+ /* Enqueue this work */
178
+ queue . add ( work )
179
+
180
+ /* Fill the queue.
181
+ * The over-fill check above protects me.
182
+ * Sure, it is possible to exceed the stack depth.
183
+ * If you are trying to run ~10K tests in parallel
184
+ * on a system where that is actually faster,
185
+ * I want to talk to you.
186
+ * It is true that node can be configured to process
187
+ * > 10K HTTP requests no problem,
188
+ * but even the decrypt tests require that you first
189
+ * encrypt something locally before making the http call.
190
+ */
191
+ enqueue ( )
192
+ }
126
193
}
127
194
128
195
interface TestVectorResults {
0 commit comments