turtb 0.5.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/LICENSE +661 -0
- package/bin/turtb.js +259 -0
- package/lib/display/h.js +204 -0
- package/lib/display/helpers.js +78 -0
- package/lib/display/render.js +177 -0
- package/lib/display/shapes.js +91 -0
- package/lib/turtle/Signer.js +120 -0
- package/lib/turtle/Signer.test.js +38 -0
- package/lib/turtle/TurtleBranch.js +147 -0
- package/lib/turtle/TurtleBranch.test.js +89 -0
- package/lib/turtle/TurtleDictionary.js +157 -0
- package/lib/turtle/TurtleDictionary.test.js +331 -0
- package/lib/turtle/U8aTurtle.js +203 -0
- package/lib/turtle/U8aTurtle.test.js +60 -0
- package/lib/turtle/Workspace.js +62 -0
- package/lib/turtle/Workspace.test.js +63 -0
- package/lib/turtle/codecs/CodecType.js +36 -0
- package/lib/turtle/codecs/CodecTypeVersion.js +37 -0
- package/lib/turtle/codecs/Commit.js +10 -0
- package/lib/turtle/codecs/CompositeCodec.js +86 -0
- package/lib/turtle/codecs/TreeNode.js +38 -0
- package/lib/turtle/codecs/codec.js +441 -0
- package/lib/turtle/connections/AbstractUpdater.js +176 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.js +102 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.test.js +26 -0
- package/lib/turtle/connections/TurtleBranchUpdater.js +47 -0
- package/lib/turtle/connections/TurtleDB.js +165 -0
- package/lib/turtle/connections/TurtleDB.test.js +45 -0
- package/lib/turtle/connections/TurtleTalker.js +34 -0
- package/lib/turtle/connections/TurtleTalker.test.js +101 -0
- package/lib/turtle/utils.js +192 -0
- package/lib/turtle/utils.test.js +158 -0
- package/lib/utils/Assert.js +115 -0
- package/lib/utils/NestedSet.js +68 -0
- package/lib/utils/NestedSet.test.js +30 -0
- package/lib/utils/OWN_KEYS.js +1 -0
- package/lib/utils/Recaller.js +175 -0
- package/lib/utils/Recaller.test.js +75 -0
- package/lib/utils/TestRunner.js +200 -0
- package/lib/utils/TestRunner.test.js +144 -0
- package/lib/utils/TestRunnerConstants.js +13 -0
- package/lib/utils/combineUint8ArrayLikes.js +18 -0
- package/lib/utils/combineUint8Arrays.js +23 -0
- package/lib/utils/components.js +88 -0
- package/lib/utils/crypto.js +17 -0
- package/lib/utils/deepEqual.js +16 -0
- package/lib/utils/deepEqual.test.js +27 -0
- package/lib/utils/fileTransformer.js +16 -0
- package/lib/utils/handleRedirect.js +93 -0
- package/lib/utils/logger.js +24 -0
- package/lib/utils/nextTick.js +47 -0
- package/lib/utils/noble-secp256k1.js +602 -0
- package/lib/utils/proxyWithRecaller.js +51 -0
- package/lib/utils/toCombinedVersion.js +14 -0
- package/lib/utils/toSubVersions.js +14 -0
- package/lib/utils/toVersionCount.js +5 -0
- package/lib/utils/webSocketMuxFactory.js +123 -0
- package/lib/utils/zabacaba.js +25 -0
- package/package.json +24 -0
- package/src/ArchiveUpdater.js +99 -0
- package/src/S3Updater.js +99 -0
- package/src/archiveSync.js +28 -0
- package/src/fileSync.js +155 -0
- package/src/getExistenceLength.js +19 -0
- package/src/manageCert.js +36 -0
- package/src/originSync.js +75 -0
- package/src/outletSync.js +50 -0
- package/src/proxyFolder.js +195 -0
- package/src/s3Sync.js +32 -0
- package/src/webSync.js +101 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { logError, logInfo, logWarn } from './logger.js'
|
|
2
|
+
import { NestedSet } from './NestedSet.js'
|
|
3
|
+
import { nextTick, getTickCount } from './nextTick.js'
|
|
4
|
+
|
|
5
|
+
let _debug = false
|
|
6
|
+
export const setDebug = (debug = true) => {
|
|
7
|
+
_debug = debug
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const IGNORE_ACCESS = Symbol('IGNORE_ACCESS')
|
|
11
|
+
export const IGNORE_MUTATE = Symbol('IGNORE_MUTATE')
|
|
12
|
+
export const IGNORE = Symbol('IGNORE')
|
|
13
|
+
|
|
14
|
+
const invert = msg => `\x1b[7m${msg}\x1b[0m`
|
|
15
|
+
|
|
16
|
+
export class Recaller {
|
|
17
|
+
name
|
|
18
|
+
#mms = new NestedSet()
|
|
19
|
+
#functionNames = new Map()
|
|
20
|
+
#stack = []
|
|
21
|
+
#triggered = new Set()
|
|
22
|
+
#handlingTriggered = false
|
|
23
|
+
#beforeTriggered = []
|
|
24
|
+
#afterTriggered = []
|
|
25
|
+
/** @type {boolean} */
|
|
26
|
+
#debug
|
|
27
|
+
#ignore = null
|
|
28
|
+
loopWarn = 2
|
|
29
|
+
loopLimit = 10
|
|
30
|
+
|
|
31
|
+
constructor (name) {
|
|
32
|
+
if (!name) throw new Error('Recaller must be named')
|
|
33
|
+
this.name = name
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
set debug (debug) {
|
|
37
|
+
this.#debug = debug
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get debug () {
|
|
41
|
+
return this.#debug ?? _debug
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
call (f, ignore = null) {
|
|
45
|
+
if (typeof f !== 'function') throw new Error('can only hide functions')
|
|
46
|
+
const previousIgnore = this.#ignore
|
|
47
|
+
this.#ignore = ignore
|
|
48
|
+
const v = f(this)
|
|
49
|
+
this.#ignore = previousIgnore
|
|
50
|
+
return v
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
watch (name, f) {
|
|
54
|
+
if (!name || typeof name !== 'string') throw new Error('please name watches')
|
|
55
|
+
if (typeof f !== 'function') { throw new Error(`can only watch functions (${name})`) }
|
|
56
|
+
this.#disassociateF(f)
|
|
57
|
+
if (!name) throw new Error('must name function watchers')
|
|
58
|
+
this.#nameFunction(f, name)
|
|
59
|
+
if (this.debug) logInfo(() => console.group(`${invert('<== watching')}: ${JSON.stringify(name)}`))
|
|
60
|
+
this.#stack.unshift(f)
|
|
61
|
+
try {
|
|
62
|
+
f(this)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logError(() => console.error(error))
|
|
65
|
+
}
|
|
66
|
+
this.#stack.shift()
|
|
67
|
+
if (this.debug) logInfo(() => console.groupEnd())
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
unwatch (f) {
|
|
71
|
+
this.#disassociateF(f)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
beforeNextUpdate (f) {
|
|
75
|
+
if (!this.#beforeTriggered.includes(f)) this.#beforeTriggered.push(f)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
afterNextUpdate (f) {
|
|
79
|
+
if (!this.#afterTriggered.includes(f)) this.#afterTriggered.push(f)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
reportKeyAccess (target, key, method, name) {
|
|
83
|
+
if (this.#ignore === IGNORE || this.#ignore === IGNORE_ACCESS) return
|
|
84
|
+
const f = this.#stack[0]
|
|
85
|
+
if (typeof f !== 'function') return
|
|
86
|
+
name = `${name}['${key.toString()}']`
|
|
87
|
+
if (this.debug) {
|
|
88
|
+
const triggering = this.#getFunctionName(f)
|
|
89
|
+
logInfo(() => console.debug(
|
|
90
|
+
`${invert('<-- access')}: ${JSON.stringify({ recaller: this.name, method, name, triggering })}`
|
|
91
|
+
))
|
|
92
|
+
}
|
|
93
|
+
this.#associate(f, target, key)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
reportKeyMutation (target, key, method, name) {
|
|
97
|
+
if (this.#ignore === IGNORE || this.#ignore === IGNORE_MUTATE) return
|
|
98
|
+
const newTriggered = this.#getFunctions(target, key)
|
|
99
|
+
if (!newTriggered.length) return
|
|
100
|
+
name = `${name}['${key.toString()}']`
|
|
101
|
+
if (this.debug) {
|
|
102
|
+
const triggering = newTriggered.map(f => this.#getFunctionName(f))
|
|
103
|
+
logInfo(() => console.debug(
|
|
104
|
+
`${invert('--> mutation')}: ${JSON.stringify({ recaller: this.name, method, name, triggering })}`
|
|
105
|
+
))
|
|
106
|
+
}
|
|
107
|
+
if (name.match(/\['name'\]\['name'\]/)) throw new Error('double name')
|
|
108
|
+
this.#triggered = new Set([...this.#triggered, ...newTriggered])
|
|
109
|
+
if (this.#handlingTriggered) return
|
|
110
|
+
this.#handlingTriggered = true
|
|
111
|
+
nextTick(() => this.#handleTriggered())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#associate (f, target, key) {
|
|
115
|
+
this.#mms.add(key, target, f)
|
|
116
|
+
this.#mms.add(f, target, key)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#disassociateF (f) {
|
|
120
|
+
this.#mms.delete(f)
|
|
121
|
+
this.#functionNames.delete(f)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#getFunctions (target, key) {
|
|
125
|
+
return this.#mms.values(key, target)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// debugging functions
|
|
129
|
+
#nameFunction (f, name) {
|
|
130
|
+
this.#functionNames.set(f, name)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#getFunctionName (f) {
|
|
134
|
+
return this.#functionNames.get(f) || f.name || `<<UNNAMED FUNCTION[${f.toString()}]>>`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#handleTriggered () {
|
|
138
|
+
const beforeTriggered = [...this.#beforeTriggered]
|
|
139
|
+
this.#beforeTriggered = []
|
|
140
|
+
beforeTriggered.forEach(f => f())
|
|
141
|
+
let loopCounter = 0
|
|
142
|
+
while ((this.#triggered.size || this.#afterTriggered.length)) {
|
|
143
|
+
if (loopCounter >= this.loopLimit) {
|
|
144
|
+
logError(() => console.error(`!! Recaller limit check ERROR; loop count: ${loopCounter}, loop limit: ${this.loopLimit}`))
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
if (loopCounter >= this.loopWarn) {
|
|
148
|
+
logWarn(() => console.warn(`!! Recaller loop count: ${loopCounter}`))
|
|
149
|
+
}
|
|
150
|
+
const triggered = this.#triggered
|
|
151
|
+
this.#triggered = new Set()
|
|
152
|
+
if (this.debug) {
|
|
153
|
+
const triggering = [...triggered].map(f => this.#getFunctionName(f))
|
|
154
|
+
logInfo(() => console.time(invert('=== handling triggered group')))
|
|
155
|
+
logInfo(() => console.groupCollapsed(
|
|
156
|
+
`${invert('==> triggering')}: ${JSON.stringify({ recaller: this.name, tickCount: getTickCount(), loopCounter, triggering })}`
|
|
157
|
+
))
|
|
158
|
+
}
|
|
159
|
+
triggered.forEach(f => {
|
|
160
|
+
const name = this.#getFunctionName(f)
|
|
161
|
+
this.#disassociateF(f)
|
|
162
|
+
this.watch(name, f)
|
|
163
|
+
})
|
|
164
|
+
while (this.#afterTriggered.length) {
|
|
165
|
+
this.#afterTriggered.shift()()
|
|
166
|
+
}
|
|
167
|
+
++loopCounter
|
|
168
|
+
if (this.debug) {
|
|
169
|
+
logInfo(() => console.groupEnd())
|
|
170
|
+
logInfo(() => console.timeEnd(invert('=== handling triggered group')))
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
this.#handlingTriggered = false
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Recaller } from './Recaller.js'
|
|
2
|
+
import { globalTestRunner, urlToName } from './TestRunner.js'
|
|
3
|
+
import { handleNextTick } from './nextTick.js'
|
|
4
|
+
|
|
5
|
+
globalTestRunner.describe(urlToName(import.meta.url), suite => {
|
|
6
|
+
suite.it('calls watched functions when accessed values change', ({ assert }) => {
|
|
7
|
+
const recaller = new Recaller('calls watched functions')
|
|
8
|
+
const a = {}
|
|
9
|
+
const b = {}
|
|
10
|
+
let counter = 0
|
|
11
|
+
recaller.watch('count on access', function () {
|
|
12
|
+
++counter
|
|
13
|
+
recaller.reportKeyAccess(a, 'x')
|
|
14
|
+
recaller.reportKeyAccess(b, 'y')
|
|
15
|
+
})
|
|
16
|
+
assert.equal(counter, 1, 'function should get called at start')
|
|
17
|
+
|
|
18
|
+
recaller.reportKeyMutation(a, 'x')
|
|
19
|
+
recaller.reportKeyMutation(a, 'x')
|
|
20
|
+
recaller.reportKeyMutation(b, 'y')
|
|
21
|
+
handleNextTick()
|
|
22
|
+
assert.equal(counter, 2, 'function should get called once per tic')
|
|
23
|
+
|
|
24
|
+
recaller.reportKeyMutation(a, 'y')
|
|
25
|
+
recaller.reportKeyMutation(a, 'y')
|
|
26
|
+
recaller.reportKeyMutation(b, 'x')
|
|
27
|
+
handleNextTick()
|
|
28
|
+
assert.equal(counter, 2, 'function should not get called when unaccessed values change')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
suite.it('calls beforeNextUpdate and afterNextUpdate functions in order', ({ assert }) => {
|
|
32
|
+
const recaller = new Recaller('calls beforeNextUpdate and afterNextUpdate')
|
|
33
|
+
const o = {}
|
|
34
|
+
let output = ''
|
|
35
|
+
recaller.beforeNextUpdate(() => {
|
|
36
|
+
output = output + 'b'
|
|
37
|
+
})
|
|
38
|
+
recaller.afterNextUpdate(() => {
|
|
39
|
+
output = output + 'a'
|
|
40
|
+
})
|
|
41
|
+
recaller.watch('o.x', () => {
|
|
42
|
+
recaller.reportKeyAccess(o, 'x')
|
|
43
|
+
output = output + 'w'
|
|
44
|
+
})
|
|
45
|
+
assert.equal(output, 'w', 'called at start')
|
|
46
|
+
handleNextTick()
|
|
47
|
+
assert.equal(output, 'w', 'nothing triggered')
|
|
48
|
+
recaller.reportKeyMutation(o, 'x')
|
|
49
|
+
assert.equal(output, 'w', 'still nothing triggered')
|
|
50
|
+
handleNextTick()
|
|
51
|
+
assert.equal(output, 'wbwa', 'everything triggered in order')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
suite.it('skips replaced triggers', ({ assert }) => {
|
|
55
|
+
const recaller = new Recaller('skips replaced triggers')
|
|
56
|
+
let callCount = 0
|
|
57
|
+
const watchedFunction = () => {
|
|
58
|
+
recaller.reportKeyMutation(recaller, 'key', 'test', 'test')
|
|
59
|
+
recaller.reportKeyAccess(recaller, 'key', 'test', 'test')
|
|
60
|
+
++callCount
|
|
61
|
+
}
|
|
62
|
+
recaller.watch('watchedFunction', watchedFunction)
|
|
63
|
+
assert.equal(callCount, 1)
|
|
64
|
+
handleNextTick()
|
|
65
|
+
assert.equal(callCount, 1)
|
|
66
|
+
recaller.reportKeyMutation(recaller, 'key', 'test', 'test')
|
|
67
|
+
assert.equal(callCount, 1)
|
|
68
|
+
handleNextTick()
|
|
69
|
+
assert.equal(callCount, 2)
|
|
70
|
+
recaller.watch('watchedFunction', watchedFunction)
|
|
71
|
+
assert.equal(callCount, 3)
|
|
72
|
+
handleNextTick()
|
|
73
|
+
assert.equal(callCount, 3)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { TurtleDictionary } from '../turtle/TurtleDictionary.js'
|
|
2
|
+
import { Assert } from './Assert.js'
|
|
3
|
+
import { logError } from './logger.js'
|
|
4
|
+
import { Recaller } from './Recaller.js'
|
|
5
|
+
import { ERROR, FAILED, ONLY, PASSED, RUNNER, RUNNING, SKIP, SKIPPED, SUITE, TEST, WAITING } from './TestRunnerConstants.js'
|
|
6
|
+
|
|
7
|
+
export function urlToName (url) {
|
|
8
|
+
if (typeof window !== 'undefined' && window?.location?.origin && url.startsWith(window.location.origin)) {
|
|
9
|
+
url = url.slice(window.location.origin.length)
|
|
10
|
+
const slashCpk = url.match(/\/[0-9A-Za-z]{50}(?=\/)/)?.[0]
|
|
11
|
+
url = url.slice(slashCpk.length)
|
|
12
|
+
}
|
|
13
|
+
url = /(?<=\/public\/).*/.exec(url)?.[0] ?? url
|
|
14
|
+
const parsed = /(?<path>[^?]*)\.test\.js?.*address=(?<address>[^&]*)/.exec(url)
|
|
15
|
+
if (parsed) url = `${parsed.groups.path} [&${parsed.groups.address}]`
|
|
16
|
+
return url
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const testRunnerRecaller = new Recaller('TestRunner')
|
|
20
|
+
// testRunnerRecaller.debug = true
|
|
21
|
+
|
|
22
|
+
export class TestRunnerError extends Error {
|
|
23
|
+
constructor (message, options) {
|
|
24
|
+
super(message, options)
|
|
25
|
+
this.name = this.constructor.name
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class TestRunner {
|
|
30
|
+
/** @type {Array.<TestRunner>} */
|
|
31
|
+
#children = []
|
|
32
|
+
/** @type {Promise} */
|
|
33
|
+
#runPromise
|
|
34
|
+
#runChildrenPromise
|
|
35
|
+
#runState
|
|
36
|
+
#f
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} [name='test-collection']
|
|
40
|
+
* @param {string} [type=RUNNER]
|
|
41
|
+
* @param {TestRunner} parent
|
|
42
|
+
* @param {() => void} f
|
|
43
|
+
* @param {Recaller} [recaller=testRunnerRecaller]
|
|
44
|
+
* @param {number} [verbose=0]
|
|
45
|
+
*/
|
|
46
|
+
constructor (
|
|
47
|
+
name = 'unnamed-test-runner',
|
|
48
|
+
type = RUNNER,
|
|
49
|
+
parent,
|
|
50
|
+
f,
|
|
51
|
+
recaller = testRunnerRecaller,
|
|
52
|
+
verbose = 0
|
|
53
|
+
) {
|
|
54
|
+
this.parent = parent
|
|
55
|
+
this.name = name
|
|
56
|
+
this.type = type
|
|
57
|
+
this.#f = f
|
|
58
|
+
this.recaller = recaller
|
|
59
|
+
this.verbose = verbose
|
|
60
|
+
this.#runState = WAITING
|
|
61
|
+
this.assert = new Assert(this)
|
|
62
|
+
this.runIndex = 0
|
|
63
|
+
this.upserter = new TurtleDictionary(name, recaller)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async run () {
|
|
67
|
+
this.#runPromise ??= (async () => {
|
|
68
|
+
this.runState = RUNNING
|
|
69
|
+
try {
|
|
70
|
+
if (this.#f) await this.#f(this)
|
|
71
|
+
await this.runChildren()
|
|
72
|
+
} catch (error) {
|
|
73
|
+
this.error = error
|
|
74
|
+
if (this.verbose) logError(() => console.error(error))
|
|
75
|
+
if (!(error instanceof TestRunnerError)) {
|
|
76
|
+
logError(() => console.error(error))
|
|
77
|
+
this.caught(`caught error: ${error.message}`, () => { throw new TestRunnerError(`${this.name}.run`, { cause: error }) })
|
|
78
|
+
}
|
|
79
|
+
this.runState = FAILED
|
|
80
|
+
}
|
|
81
|
+
})()
|
|
82
|
+
return this.#runPromise
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async runChildren () {
|
|
86
|
+
this.#runChildrenPromise ??= (async () => {
|
|
87
|
+
++this.runIndex
|
|
88
|
+
const errors = []
|
|
89
|
+
const hasOnly = this.#children.some(child => child.type === ONLY)
|
|
90
|
+
for (const child of this.#children) {
|
|
91
|
+
if (hasOnly && child.type !== ONLY) child.runState = SKIPPED
|
|
92
|
+
if (child.type === SKIP) child.runState = SKIPPED
|
|
93
|
+
if (child.runState !== SKIPPED) await child.run()
|
|
94
|
+
if (child.runState === FAILED) errors.push(child.error)
|
|
95
|
+
}
|
|
96
|
+
if (errors.length) {
|
|
97
|
+
throw new TestRunnerError(`${this.name}.runChildren`, { cause: errors })
|
|
98
|
+
}
|
|
99
|
+
this.runState = PASSED
|
|
100
|
+
})()
|
|
101
|
+
return this.#runChildrenPromise
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get status () {
|
|
105
|
+
const status = {
|
|
106
|
+
name: this.name,
|
|
107
|
+
type: this.type,
|
|
108
|
+
runState: this.runState,
|
|
109
|
+
children: this.children.map(child => child.status)
|
|
110
|
+
}
|
|
111
|
+
return status
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get children () {
|
|
115
|
+
this.recaller.reportKeyAccess(this, 'children', 'get', this.name)
|
|
116
|
+
return this.#children
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get runState () {
|
|
120
|
+
this.recaller.reportKeyAccess(this, 'runState', 'get', this.name)
|
|
121
|
+
return this.#runState
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
set runState (runState) {
|
|
125
|
+
this.recaller.reportKeyMutation(this, 'runState', 'get', this.name)
|
|
126
|
+
this.#runState = runState
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {string} name
|
|
131
|
+
* @param {(runner: TestRunner) => any} f
|
|
132
|
+
* @param {string} type
|
|
133
|
+
* @returns {TestRunner}
|
|
134
|
+
*/
|
|
135
|
+
appendChild (name, f, type) {
|
|
136
|
+
const child = new TestRunner(name, type, this, f, this.recaller, this.verbose)
|
|
137
|
+
this.#children.push(child)
|
|
138
|
+
this.#runChildrenPromise = undefined
|
|
139
|
+
this.recaller.reportKeyMutation(this, 'children', 'appendChild', this.name)
|
|
140
|
+
return child
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
clearChildren () {
|
|
144
|
+
this.#children = []
|
|
145
|
+
this.#runChildrenPromise = undefined
|
|
146
|
+
this.recaller.reportKeyMutation(this, 'children', 'clearChildren', this.name)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} name
|
|
151
|
+
* @param {(suite: TestRunner) => any} f
|
|
152
|
+
* @returns {TestRunner}
|
|
153
|
+
*/
|
|
154
|
+
describe (name, f) {
|
|
155
|
+
return this.appendChild(name, f, SUITE)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @param {string} name
|
|
160
|
+
* @param {(test: TestRunner) => any} f
|
|
161
|
+
* @returns {TestRunner}
|
|
162
|
+
*/
|
|
163
|
+
it (name, f) {
|
|
164
|
+
return this.appendChild(name, f, TEST)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {string} name
|
|
169
|
+
* @param {(test: TestRunner) => any} f
|
|
170
|
+
* @returns {TestRunner}
|
|
171
|
+
*/
|
|
172
|
+
test (name, f) {
|
|
173
|
+
return this.appendChild(name, f, TEST)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {string} name
|
|
178
|
+
* @param {(test: TestRunner) => any} f
|
|
179
|
+
* @returns {TestRunner}
|
|
180
|
+
*/
|
|
181
|
+
caught (name, f) {
|
|
182
|
+
return this.appendChild(name, f, ERROR)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @returns {TestRunner}
|
|
187
|
+
*/
|
|
188
|
+
get only () {
|
|
189
|
+
return this.appendChild('only', () => {}, ONLY)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @returns {TestRunner}
|
|
194
|
+
*/
|
|
195
|
+
get skip () {
|
|
196
|
+
return this.appendChild('skip', () => {}, SKIP)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const globalTestRunner = new TestRunner('globalTestRunner')
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ASSERTION, PASSED, RUNNER, SUITE, TEST, WAITING } from './TestRunnerConstants.js'
|
|
2
|
+
import { globalTestRunner, TestRunner, urlToName } from './TestRunner.js'
|
|
3
|
+
|
|
4
|
+
globalTestRunner.describe(urlToName(import.meta.url), suite => {
|
|
5
|
+
suite.it('waits for tests to complete', async test => {
|
|
6
|
+
const runner = new TestRunner()
|
|
7
|
+
let _suite
|
|
8
|
+
// runnerRecaller.watch('update status', () => {
|
|
9
|
+
// console.log(JSON.stringify(runner.status, undefined, 10))
|
|
10
|
+
// })
|
|
11
|
+
runner.describe('abc', suite => {
|
|
12
|
+
_suite = suite
|
|
13
|
+
suite.it('xy', async (assert) => {
|
|
14
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
15
|
+
assert.assert.equal(1, 1, '1 === 1')
|
|
16
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
17
|
+
assert.assert.equal(2, 2, '2 === 2')
|
|
18
|
+
})
|
|
19
|
+
suite.it('z', async (assert) => {
|
|
20
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
21
|
+
assert.assert.equal(3, 3, '3 === 3')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
await runner.run()
|
|
26
|
+
test.assert.equal({
|
|
27
|
+
name: 'unnamed-test-runner',
|
|
28
|
+
type: RUNNER,
|
|
29
|
+
runState: PASSED,
|
|
30
|
+
children: [
|
|
31
|
+
{
|
|
32
|
+
name: 'abc',
|
|
33
|
+
type: SUITE,
|
|
34
|
+
runState: PASSED,
|
|
35
|
+
children: [
|
|
36
|
+
{
|
|
37
|
+
name: 'xy',
|
|
38
|
+
type: TEST,
|
|
39
|
+
runState: PASSED,
|
|
40
|
+
children: [
|
|
41
|
+
{
|
|
42
|
+
name: '1 === 1',
|
|
43
|
+
type: ASSERTION,
|
|
44
|
+
runState: PASSED,
|
|
45
|
+
children: []
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: '2 === 2',
|
|
49
|
+
type: ASSERTION,
|
|
50
|
+
runState: PASSED,
|
|
51
|
+
children: []
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'z',
|
|
57
|
+
type: TEST,
|
|
58
|
+
runState: PASSED,
|
|
59
|
+
children: [
|
|
60
|
+
{
|
|
61
|
+
name: '3 === 3',
|
|
62
|
+
type: ASSERTION,
|
|
63
|
+
runState: PASSED,
|
|
64
|
+
children: []
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}, runner.status, 'after run')
|
|
72
|
+
|
|
73
|
+
_suite.it('m', async (assert) => {
|
|
74
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
75
|
+
assert.assert.equal(4, 5, '4 !== 5', false)
|
|
76
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
runner.describe('n', async (assert) => {
|
|
80
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
81
|
+
assert.assert.equal(5, 5, '5 === 5')
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test.assert.equal({
|
|
86
|
+
name: 'unnamed-test-runner',
|
|
87
|
+
type: RUNNER,
|
|
88
|
+
runState: PASSED,
|
|
89
|
+
children: [
|
|
90
|
+
{
|
|
91
|
+
name: 'abc',
|
|
92
|
+
type: SUITE,
|
|
93
|
+
runState: PASSED,
|
|
94
|
+
children: [
|
|
95
|
+
{
|
|
96
|
+
name: 'xy',
|
|
97
|
+
type: TEST,
|
|
98
|
+
runState: PASSED,
|
|
99
|
+
children: [
|
|
100
|
+
{
|
|
101
|
+
name: '1 === 1',
|
|
102
|
+
type: ASSERTION,
|
|
103
|
+
runState: PASSED,
|
|
104
|
+
children: []
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: '2 === 2',
|
|
108
|
+
type: ASSERTION,
|
|
109
|
+
runState: PASSED,
|
|
110
|
+
children: []
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'z',
|
|
116
|
+
type: TEST,
|
|
117
|
+
runState: PASSED,
|
|
118
|
+
children: [
|
|
119
|
+
{
|
|
120
|
+
name: '3 === 3',
|
|
121
|
+
type: ASSERTION,
|
|
122
|
+
runState: PASSED,
|
|
123
|
+
children: []
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'm',
|
|
129
|
+
type: TEST,
|
|
130
|
+
runState: WAITING,
|
|
131
|
+
children: []
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'n',
|
|
137
|
+
type: SUITE,
|
|
138
|
+
runState: WAITING,
|
|
139
|
+
children: []
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}, runner.status, 'after 2nd describe')
|
|
143
|
+
})
|
|
144
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const PASSED = '☑'
|
|
2
|
+
export const FAILED = '☒'
|
|
3
|
+
export const WAITING = '□'
|
|
4
|
+
export const RUNNING = '■'
|
|
5
|
+
export const SKIPPED = '⧄'
|
|
6
|
+
|
|
7
|
+
export const RUNNER = '߷'
|
|
8
|
+
export const SUITE = '●'
|
|
9
|
+
export const TEST = '⇶'
|
|
10
|
+
export const ASSERTION = '→'
|
|
11
|
+
export const ONLY = '⮕'
|
|
12
|
+
export const SKIP = '⇏'
|
|
13
|
+
export const ERROR = '⚠'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { combineUint8Arrays } from './combineUint8Arrays.js'
|
|
2
|
+
import { logError } from './logger.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {Array.<any>} uint8ArrayLikes
|
|
6
|
+
* @returns {Uint8Array}
|
|
7
|
+
*/
|
|
8
|
+
export function combineUint8ArrayLikes (uint8ArrayLikes) {
|
|
9
|
+
if (!Array.isArray(uint8ArrayLikes)) throw new Error('friendly reminder... combineUint8ArrayLikes accepts an array of Uint8ArrayLikes')
|
|
10
|
+
const uint8Arrays = uint8ArrayLikes.map(uint8ArrayLike => {
|
|
11
|
+
if (uint8ArrayLike instanceof Uint8Array) return uint8ArrayLike
|
|
12
|
+
if (uint8ArrayLike instanceof Object.getPrototypeOf(Uint8Array)) return new Uint8Array(uint8ArrayLike.buffer)
|
|
13
|
+
if (Number.isInteger(uint8ArrayLike) && uint8ArrayLike <= 0xff) return new Uint8Array([uint8ArrayLike])
|
|
14
|
+
logError(() => console.error(uint8ArrayLikes))
|
|
15
|
+
throw new Error('can\'t convert to Uint8Array')
|
|
16
|
+
})
|
|
17
|
+
return combineUint8Arrays(uint8Arrays)
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { logError } from './logger.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Array.<Uint8Array>} uint8Arrays
|
|
5
|
+
* @returns {Uint8Array}
|
|
6
|
+
*/
|
|
7
|
+
export function combineUint8Arrays (uint8Arrays) {
|
|
8
|
+
if (!Array.isArray(uint8Arrays)) throw new Error('friendly reminder... combineUint8Arrays accepts an array of Uint8Arrays')
|
|
9
|
+
const combinedLength = uint8Arrays.reduce((length, uint8Array) => length + (uint8Array?.length ?? 0), 0)
|
|
10
|
+
const collapsedUint8Array = new Uint8Array(combinedLength)
|
|
11
|
+
let address = 0
|
|
12
|
+
for (const uint8Array of uint8Arrays) {
|
|
13
|
+
if (!(uint8Array instanceof Uint8Array)) {
|
|
14
|
+
logError(() => console.error('not Uint8Array', uint8Array))
|
|
15
|
+
throw new Error('combineUint8Arrays can only combine Uint8Arrays')
|
|
16
|
+
}
|
|
17
|
+
if (uint8Array?.length) {
|
|
18
|
+
collapsedUint8Array.set(uint8Array, address)
|
|
19
|
+
address += uint8Array.length
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return collapsedUint8Array
|
|
23
|
+
}
|