@smartledger/bsv 8.0.1 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +118 -0
- package/README.md +38 -38
- package/bsv-gdaf.min.js +47 -47
- package/bsv-ltp.min.js +1 -1
- package/bsv-smartcontract.min.js +1 -1
- package/bsv.bundle.js +48 -48
- package/bsv.min.js +48 -48
- package/docs/AUDIT_SCOPE.md +13 -13
- package/docs/MODULE_REFERENCE_COMPLETE.md +27 -27
- package/docs/advanced/UTXO_MANAGER_GUIDE.md +1 -1
- package/docs/getting-started/INSTALLATION.md +23 -23
- package/docs/getting-started/QUICK_START.md +7 -7
- package/docs/migration/FROM_BSV_1_5_6.md +5 -5
- package/lib/gdaf/attestation-signer.js +90 -2
- package/lib/gdaf/attestation-verifier.js +41 -25
- package/lib/gdaf/zk-prover.js +143 -46
- package/lib/script/interpreter.js +20 -1
- package/package.json +3 -2
- package/test/gdaf/canonicalization.js +106 -0
- package/test/gdaf/zk_prover.js +204 -0
- package/test/script/genesis_limits.js +37 -6
- package/tools/minimal_reproduction.js +119 -0
- package/tools/opcode_map.js +342 -0
- package/tools/server.js +97 -0
- package/tools/simple_real_tx.js +136 -0
- package/tools/sv-sighash-harness.js +123 -0
- package/tools/sv-sighash-report.js +71 -0
- package/tools/sv-tx-harness.js +172 -0
- package/tools/sv-tx-report.js +43 -0
- package/tools/sv-vector-harness.js +304 -0
- package/tools/sv-vector-report.js +88 -0
- package/version.js +1 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared harness for the SV Node script vectors in test/data/bitcoin-sv.
|
|
5
|
+
*
|
|
6
|
+
* These are the reference node's own test data, copied verbatim, and they are
|
|
7
|
+
* the specification this interpreter is measured against. The corpus in
|
|
8
|
+
* test/data/bitcoind is Bitcoin Core's and encodes rules BSV abandoned at
|
|
9
|
+
* Genesis in 2020; it is not a consensus reference for this library.
|
|
10
|
+
*
|
|
11
|
+
* Used by tools/sv-vector-report.js (which reports) and
|
|
12
|
+
* test/consensus/sv-script-vectors.js (which ratchets), so the progress report
|
|
13
|
+
* and the regression gate cannot disagree about what passes.
|
|
14
|
+
*
|
|
15
|
+
* On eras. The node decides most rules by the era of the *output being spent*,
|
|
16
|
+
* carried in SCRIPT_UTXO_AFTER_GENESIS and SCRIPT_UTXO_AFTER_CHRONICLE, and a
|
|
17
|
+
* few by the era of the *spending transaction*, carried in SCRIPT_GENESIS and
|
|
18
|
+
* SCRIPT_CHRONICLE.
|
|
19
|
+
*
|
|
20
|
+
* Era flags are passed through as the node's own, which is what the rows name.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const bsv = require('..')
|
|
24
|
+
const Script = bsv.Script
|
|
25
|
+
const Opcode = bsv.Opcode
|
|
26
|
+
const Interpreter = bsv.Script.Interpreter
|
|
27
|
+
const Transaction = bsv.Transaction
|
|
28
|
+
const BN = bsv.crypto.BN
|
|
29
|
+
const BufferWriter = bsv.encoding.BufferWriter
|
|
30
|
+
|
|
31
|
+
const rawVectors = require('../test/data/bitcoin-sv/script_tests.json')
|
|
32
|
+
|
|
33
|
+
// Mapped by name rather than by value. This library assigns MONOLITH and
|
|
34
|
+
// MAGNETIC to 1<<18 and 1<<19, which are the bits the node uses for
|
|
35
|
+
// SCRIPT_GENESIS and SCRIPT_UTXO_AFTER_GENESIS, so mapping by value would
|
|
36
|
+
// quietly mean something else.
|
|
37
|
+
const FLAG_MAP = {
|
|
38
|
+
NONE: 'SCRIPT_VERIFY_NONE',
|
|
39
|
+
P2SH: 'SCRIPT_VERIFY_P2SH',
|
|
40
|
+
STRICTENC: 'SCRIPT_VERIFY_STRICTENC',
|
|
41
|
+
DERSIG: 'SCRIPT_VERIFY_DERSIG',
|
|
42
|
+
LOW_S: 'SCRIPT_VERIFY_LOW_S',
|
|
43
|
+
NULLDUMMY: 'SCRIPT_VERIFY_NULLDUMMY',
|
|
44
|
+
SIGPUSHONLY: 'SCRIPT_VERIFY_SIGPUSHONLY',
|
|
45
|
+
MINIMALDATA: 'SCRIPT_VERIFY_MINIMALDATA',
|
|
46
|
+
DISCOURAGE_UPGRADABLE_NOPS: 'SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS',
|
|
47
|
+
CLEANSTACK: 'SCRIPT_VERIFY_CLEANSTACK',
|
|
48
|
+
CHECKLOCKTIMEVERIFY: 'SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY',
|
|
49
|
+
CHECKSEQUENCEVERIFY: 'SCRIPT_VERIFY_CHECKSEQUENCEVERIFY',
|
|
50
|
+
MINIMALIF: 'SCRIPT_VERIFY_MINIMALIF',
|
|
51
|
+
NULLFAIL: 'SCRIPT_VERIFY_NULLFAIL',
|
|
52
|
+
COMPRESSED_PUBKEYTYPE: 'SCRIPT_VERIFY_COMPRESSED_PUBKEYTYPE',
|
|
53
|
+
SIGHASH_FORKID: 'SCRIPT_ENABLE_SIGHASH_FORKID',
|
|
54
|
+
REPLAY_PROTECTION: 'SCRIPT_ENABLE_REPLAY_PROTECTION',
|
|
55
|
+
MONOLITH_OPCODES: 'SCRIPT_ENABLE_MONOLITH_OPCODES',
|
|
56
|
+
MAGNETIC_OPCODES: 'SCRIPT_ENABLE_MAGNETIC_OPCODES'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The corpus never names MONOLITH or MAGNETIC, because the node has no such
|
|
60
|
+
// flags — those opcodes were restored on BSV in 2018 and are simply enabled.
|
|
61
|
+
// This library still gates them, so they are enabled for every row; otherwise
|
|
62
|
+
// the report is dominated by that difference rather than by consensus.
|
|
63
|
+
// Requiring them is itself a divergence, counted separately rather than here.
|
|
64
|
+
const OPCODE_BASELINE =
|
|
65
|
+
Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES | Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Result codes this library reports more narrowly than the node does, and the
|
|
69
|
+
* node code each stands for.
|
|
70
|
+
*
|
|
71
|
+
* Matching the node's accept/reject outcome is not the same as failing for the
|
|
72
|
+
* node's reason, and the gap between the two hid real bugs: a div-by-zero
|
|
73
|
+
* guard comparing a BN against the number 0, so that it never fired and bn.js
|
|
74
|
+
* asserted instead; script number decoding throwing past the evaluator into a
|
|
75
|
+
* generic catch; a truncated PUSHDATA doing the same. All three left the
|
|
76
|
+
* script failing, just not in the way the node fails it, so the outcome check
|
|
77
|
+
* stayed green through every one.
|
|
78
|
+
*
|
|
79
|
+
* This is a short statement of intent rather than a list of exempted vectors,
|
|
80
|
+
* and it ratchets both ways — an unlisted mismatch fails, and so does an alias
|
|
81
|
+
* no vector produces any more. Anything added here should be a name that says
|
|
82
|
+
* *more* than the node's, never a different failure wearing its label.
|
|
83
|
+
*/
|
|
84
|
+
const ERROR_CODE_ALIASES = {
|
|
85
|
+
EVAL_FALSE_IN_STACK: 'EVAL_FALSE',
|
|
86
|
+
EVAL_FALSE_NO_RESULT: 'EVAL_FALSE',
|
|
87
|
+
EVAL_FALSE_NO_P2SH_STACK: 'EVAL_FALSE',
|
|
88
|
+
EVAL_FALSE_IN_P2SH_STACK: 'EVAL_FALSE',
|
|
89
|
+
SIG_DER_INVALID_FORMAT: 'SIG_DER',
|
|
90
|
+
SIG_DER_HIGH_S: 'SIG_HIGH_S',
|
|
91
|
+
INVALID_OPERAND_SIZE: 'OPERAND_SIZE',
|
|
92
|
+
INVALID_SPLIT_RANGE: 'SPLIT_RANGE'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The bare result code, without the prefix or any appended detail. */
|
|
96
|
+
function errorCode (errstr) {
|
|
97
|
+
return String(errstr || '').replace(/^SCRIPT_ERR_/, '').split(':')[0].trim()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Does what we reported match the node's `expected` code, or alias to it? */
|
|
101
|
+
function errorCodeMatches (expected, errstr) {
|
|
102
|
+
const got = errorCode(errstr)
|
|
103
|
+
return got === expected || ERROR_CODE_ALIASES[got] === expected
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Parse a bitcoind-format script string, e.g. "0x47 0x3044... CHECKSIG". */
|
|
107
|
+
function fromBitcoindString (str) {
|
|
108
|
+
const bw = new BufferWriter()
|
|
109
|
+
for (const token of String(str).split(' ')) {
|
|
110
|
+
if (token === '') continue
|
|
111
|
+
if (token[0] === '0' && token[1] === 'x') {
|
|
112
|
+
bw.write(Buffer.from(token.slice(2), 'hex'))
|
|
113
|
+
} else if (token[0] === "'") {
|
|
114
|
+
bw.write(Script().add(Buffer.from(token.slice(1, token.length - 1))).toBuffer())
|
|
115
|
+
} else if (typeof Opcode['OP_' + token] !== 'undefined') {
|
|
116
|
+
bw.writeUInt8(Opcode['OP_' + token])
|
|
117
|
+
} else if (typeof Opcode[token] === 'number') {
|
|
118
|
+
bw.writeUInt8(Opcode[token])
|
|
119
|
+
} else if (!isNaN(parseInt(token))) {
|
|
120
|
+
bw.write(Script().add(new BN(token).toScriptNumBuffer()).toBuffer())
|
|
121
|
+
} else {
|
|
122
|
+
const err = new Error('unknown script token: ' + token)
|
|
123
|
+
err.token = token
|
|
124
|
+
throw err
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return Script.fromBuffer(bw.concat())
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Row layout, from script_json_test in the node's src/test/script_tests.cpp:
|
|
132
|
+
*
|
|
133
|
+
* [ [nValue]?, txnVersion, scriptSig, scriptPubKey, flags, expected, comment? ]
|
|
134
|
+
*
|
|
135
|
+
* nValue appears only where a row needs an amount and is array-wrapped in
|
|
136
|
+
* whole coins. txnVersion is the spending transaction's version, which
|
|
137
|
+
* Chronicle uses to gate its malleability relaxations — it is not padding and
|
|
138
|
+
* it is not an amount.
|
|
139
|
+
*/
|
|
140
|
+
function parseRow (row) {
|
|
141
|
+
let pos = 0
|
|
142
|
+
let value = 0
|
|
143
|
+
if (Array.isArray(row[0])) {
|
|
144
|
+
value = Math.round(parseFloat(row[0][0]) * 1e8)
|
|
145
|
+
pos++
|
|
146
|
+
}
|
|
147
|
+
if (row.length < 4 + pos) return null
|
|
148
|
+
return {
|
|
149
|
+
version: parseInt(row[pos], 10),
|
|
150
|
+
scriptSig: row[pos + 1],
|
|
151
|
+
scriptPubKey: row[pos + 2],
|
|
152
|
+
flagStr: row[pos + 3],
|
|
153
|
+
expected: row[pos + 4],
|
|
154
|
+
satoshis: value,
|
|
155
|
+
comment: row.slice(pos + 5).join(' ')
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function classifyFlags (flagStr) {
|
|
160
|
+
let flags = OPCODE_BASELINE
|
|
161
|
+
const unknown = []
|
|
162
|
+
for (const raw of String(flagStr).split(',')) {
|
|
163
|
+
const name = raw.trim()
|
|
164
|
+
if (!name) continue
|
|
165
|
+
if (FLAG_MAP[name] !== undefined) {
|
|
166
|
+
flags |= Interpreter[FLAG_MAP[name]]
|
|
167
|
+
} else if (name === 'GENESIS') {
|
|
168
|
+
flags |= Interpreter.SCRIPT_GENESIS
|
|
169
|
+
} else if (name === 'UTXO_AFTER_GENESIS') {
|
|
170
|
+
flags |= Interpreter.SCRIPT_UTXO_AFTER_GENESIS
|
|
171
|
+
} else if (name === 'UTXO_AFTER_CHRONICLE') {
|
|
172
|
+
flags |= Interpreter.SCRIPT_UTXO_AFTER_CHRONICLE
|
|
173
|
+
} else {
|
|
174
|
+
unknown.push(name)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { flags, unknown }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build and verify the crediting/spending transaction pair, mirroring
|
|
182
|
+
* BuildCreditingTransaction and BuildSpendingTransaction in the node's
|
|
183
|
+
* src/test/script_tests.cpp. The crediting transaction is always version 1;
|
|
184
|
+
* only the spending transaction carries the version under test.
|
|
185
|
+
*/
|
|
186
|
+
function evaluate (row, flags) {
|
|
187
|
+
const scriptSig = fromBitcoindString(row.scriptSig)
|
|
188
|
+
const scriptPubKey = fromBitcoindString(row.scriptPubKey)
|
|
189
|
+
|
|
190
|
+
const credtx = new Transaction()
|
|
191
|
+
credtx.version = 1
|
|
192
|
+
credtx.nLockTime = 0
|
|
193
|
+
credtx.uncheckedAddInput(new Transaction.Input({
|
|
194
|
+
prevTxId: '0'.repeat(64),
|
|
195
|
+
outputIndex: 0xffffffff,
|
|
196
|
+
sequenceNumber: 0xffffffff,
|
|
197
|
+
script: Script('OP_0 OP_0')
|
|
198
|
+
}))
|
|
199
|
+
credtx.addOutput(new Transaction.Output({ script: scriptPubKey, satoshis: row.satoshis }))
|
|
200
|
+
|
|
201
|
+
const spendtx = new Transaction()
|
|
202
|
+
spendtx.version = row.version
|
|
203
|
+
spendtx.nLockTime = 0
|
|
204
|
+
spendtx.uncheckedAddInput(new Transaction.Input({
|
|
205
|
+
prevTxId: credtx.id.toString('hex'),
|
|
206
|
+
outputIndex: 0,
|
|
207
|
+
sequenceNumber: 0xffffffff,
|
|
208
|
+
script: scriptSig
|
|
209
|
+
}))
|
|
210
|
+
spendtx.addOutput(new Transaction.Output({ script: Script(), satoshis: row.satoshis }))
|
|
211
|
+
|
|
212
|
+
const interp = new Interpreter()
|
|
213
|
+
const ok = interp.verify(scriptSig, scriptPubKey, spendtx, 0, flags, new BN(row.satoshis))
|
|
214
|
+
return { ok, errstr: interp.errstr }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** A stable id for a row, so a known-failure list survives reordering. */
|
|
218
|
+
function vectorId (raw) {
|
|
219
|
+
return require('crypto').createHash('sha256')
|
|
220
|
+
.update(JSON.stringify(raw)).digest('hex').slice(0, 12)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Run every vector. Each result is
|
|
225
|
+
*
|
|
226
|
+
* { id, row, passed, reason, direction, gotErrstr }
|
|
227
|
+
*
|
|
228
|
+
* `direction` is 'accept' where this library accepted a script the node
|
|
229
|
+
* rejects — the direction that can cost money — and 'reject' for the converse.
|
|
230
|
+
*/
|
|
231
|
+
function runAll () {
|
|
232
|
+
const results = []
|
|
233
|
+
for (const raw of rawVectors) {
|
|
234
|
+
if (!Array.isArray(raw) || raw.length < 4) continue
|
|
235
|
+
const row = parseRow(raw)
|
|
236
|
+
if (row === null) continue
|
|
237
|
+
|
|
238
|
+
const cls = classifyFlags(row.flagStr)
|
|
239
|
+
const expectOk = row.expected === 'OK'
|
|
240
|
+
|
|
241
|
+
let got = null
|
|
242
|
+
let thrown = null
|
|
243
|
+
try {
|
|
244
|
+
got = evaluate(row, cls.flags)
|
|
245
|
+
} catch (e) {
|
|
246
|
+
thrown = e
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let reason = null
|
|
250
|
+
let direction = null
|
|
251
|
+
if (thrown !== null) {
|
|
252
|
+
reason = 'threw: ' + String(thrown.message).slice(0, 70)
|
|
253
|
+
direction = 'reject'
|
|
254
|
+
} else if (got.ok !== expectOk) {
|
|
255
|
+
if (expectOk) {
|
|
256
|
+
reason = 'expected OK, rejected with ' + got.errstr
|
|
257
|
+
direction = 'reject'
|
|
258
|
+
} else {
|
|
259
|
+
reason = 'expected ' + row.expected + ', but ACCEPTED'
|
|
260
|
+
direction = 'accept'
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Only meaningful where the node rejects and so does this library: there
|
|
265
|
+
// is no code to compare on a row the node accepts, and a row whose outcome
|
|
266
|
+
// already disagrees is reported as that rather than twice.
|
|
267
|
+
const comparable = !expectOk && reason === null
|
|
268
|
+
results.push({
|
|
269
|
+
id: vectorId(raw),
|
|
270
|
+
row,
|
|
271
|
+
unknownFlags: cls.unknown,
|
|
272
|
+
gotErrstr: got ? got.errstr : null,
|
|
273
|
+
passed: reason === null,
|
|
274
|
+
reason,
|
|
275
|
+
direction,
|
|
276
|
+
expectedCode: comparable ? row.expected : null,
|
|
277
|
+
gotCode: comparable ? errorCode(got.errstr) : null,
|
|
278
|
+
codeMatches: comparable ? errorCodeMatches(row.expected, got.errstr) : null
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
return results
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** A one-line description of a row, for failure output. */
|
|
285
|
+
function describe (row) {
|
|
286
|
+
return String(row.scriptSig).slice(0, 40) + ' | ' +
|
|
287
|
+
String(row.scriptPubKey).slice(0, 40) +
|
|
288
|
+
' [' + row.flagStr + ']' +
|
|
289
|
+
(row.comment ? ' // ' + row.comment.slice(0, 50) : '')
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = {
|
|
293
|
+
runAll,
|
|
294
|
+
evaluate,
|
|
295
|
+
describe,
|
|
296
|
+
parseRow,
|
|
297
|
+
vectorId,
|
|
298
|
+
classifyFlags,
|
|
299
|
+
fromBitcoindString,
|
|
300
|
+
errorCode,
|
|
301
|
+
errorCodeMatches,
|
|
302
|
+
ERROR_CODE_ALIASES,
|
|
303
|
+
FLAG_MAP
|
|
304
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* How far this interpreter is from the reference node.
|
|
5
|
+
*
|
|
6
|
+
* npm run vectors:sv
|
|
7
|
+
* npm run vectors:sv -- --verbose
|
|
8
|
+
*
|
|
9
|
+
* False accepts are listed first and separately. Accepting a script the
|
|
10
|
+
* network rejects is the direction that can cost money; rejecting one it
|
|
11
|
+
* accepts only costs a transaction.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const harness = require('./sv-vector-harness')
|
|
15
|
+
|
|
16
|
+
const verbose = process.argv.indexOf('--verbose') !== -1
|
|
17
|
+
const results = harness.runAll()
|
|
18
|
+
|
|
19
|
+
const failing = results.filter(r => !r.passed)
|
|
20
|
+
const accepts = failing.filter(r => r.direction === 'accept')
|
|
21
|
+
const rejects = failing.filter(r => r.direction === 'reject')
|
|
22
|
+
const passing = results.length - failing.length
|
|
23
|
+
|
|
24
|
+
console.log()
|
|
25
|
+
console.log('=== SV Node v1.2.0 script vectors ===')
|
|
26
|
+
console.log()
|
|
27
|
+
console.log('vectors run :', results.length)
|
|
28
|
+
console.log('passing :', passing,
|
|
29
|
+
' (' + (100 * passing / results.length).toFixed(1) + '%)')
|
|
30
|
+
console.log('failing :', failing.length,
|
|
31
|
+
' (' + (100 * failing.length / results.length).toFixed(1) + '%)')
|
|
32
|
+
console.log()
|
|
33
|
+
console.log('--- direction ---')
|
|
34
|
+
console.log(' false accepts (we accept, node rejects) :', accepts.length,
|
|
35
|
+
accepts.length ? ' <- the direction that can cost money' : '')
|
|
36
|
+
console.log(' false rejects (we reject, node accepts) :', rejects.length)
|
|
37
|
+
|
|
38
|
+
const comparable = results.filter(r => r.codeMatches !== null)
|
|
39
|
+
const wrongCode = comparable.filter(r => !r.codeMatches)
|
|
40
|
+
const aliased = comparable.filter(r => r.codeMatches && r.gotCode !== r.expectedCode)
|
|
41
|
+
console.log()
|
|
42
|
+
console.log('--- result codes, on the', comparable.length, 'vectors the node rejects ---')
|
|
43
|
+
console.log(' exact match :',
|
|
44
|
+
comparable.length - wrongCode.length - aliased.length)
|
|
45
|
+
console.log(' matched via a documented narrower name :', aliased.length)
|
|
46
|
+
console.log(' wrong reason :', wrongCode.length,
|
|
47
|
+
wrongCode.length ? ' <- the outcome is right, the reason is not' : '')
|
|
48
|
+
|
|
49
|
+
if (wrongCode.length) {
|
|
50
|
+
const byCode = {}
|
|
51
|
+
wrongCode.forEach(function (r) {
|
|
52
|
+
const key = r.expectedCode + ' <- ' + (r.gotCode || '(none)')
|
|
53
|
+
byCode[key] = (byCode[key] || 0) + 1
|
|
54
|
+
})
|
|
55
|
+
Object.keys(byCode).sort((a, b) => byCode[b] - byCode[a])
|
|
56
|
+
.forEach(k => console.log(String(byCode[k]).padStart(5), k))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const byReason = {}
|
|
60
|
+
failing.forEach(function (r) {
|
|
61
|
+
const key = String(r.reason).replace(/vector.*/, '').slice(0, 62)
|
|
62
|
+
byReason[key] = (byReason[key] || 0) + 1
|
|
63
|
+
})
|
|
64
|
+
console.log()
|
|
65
|
+
console.log('--- failure reasons ---')
|
|
66
|
+
Object.keys(byReason).sort((a, b) => byReason[b] - byReason[a]).slice(0, 16)
|
|
67
|
+
.forEach(k => console.log(String(byReason[k]).padStart(5), k))
|
|
68
|
+
|
|
69
|
+
if (accepts.length) {
|
|
70
|
+
console.log()
|
|
71
|
+
console.log('--- false accepts ---')
|
|
72
|
+
accepts.slice(0, verbose ? accepts.length : 10).forEach(function (r) {
|
|
73
|
+
console.log(' ' + r.id + ' ' + harness.describe(r.row))
|
|
74
|
+
})
|
|
75
|
+
if (!verbose && accepts.length > 10) {
|
|
76
|
+
console.log(' ... and ' + (accepts.length - 10) + ' more; pass --verbose')
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (verbose) {
|
|
81
|
+
console.log()
|
|
82
|
+
console.log('--- every failing vector ---')
|
|
83
|
+
failing.forEach(function (r) {
|
|
84
|
+
console.log(' ' + r.id + ' ' + r.reason)
|
|
85
|
+
console.log(' ' + harness.describe(r.row))
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
console.log()
|
package/version.js
CHANGED