mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
WIP
This commit is contained in:
@@ -26,8 +26,6 @@ const io_patch = (path, use_context = false) => {
|
||||
|
||||
const original = obj[method]
|
||||
obj[method] = function(...args) {
|
||||
|
||||
// TODO does it work? After we change for global cxt? Review all cxt usages
|
||||
if(cxt.io_cache_is_replay_aborted) {
|
||||
// Try to finish fast
|
||||
// TODO invoke callback to notify that code must be restarted?
|
||||
@@ -217,8 +215,6 @@ const io_patch = (path, use_context = false) => {
|
||||
obj[method].__original = original
|
||||
}
|
||||
|
||||
// TODO bare IO functions should not be exposed at all, to allow calling it
|
||||
// only from patched versions. Especially setInterval which can cause leaks
|
||||
export const apply_io_patches = () => {
|
||||
// TODO remove, only for dev
|
||||
// TODO test open_run_window
|
||||
@@ -235,7 +231,6 @@ export const apply_io_patches = () => {
|
||||
// replaying from cache
|
||||
io_patch(['clearTimeout'])
|
||||
|
||||
|
||||
// TODO patch setInterval to only cleanup all intervals on finish
|
||||
|
||||
const Date = cxt.window.Date
|
||||
|
||||
@@ -96,10 +96,6 @@ const do_run = function*(module_fns, cxt, io_cache){
|
||||
}
|
||||
|
||||
export const run = gen_to_promise(function*(module_fns, cxt, io_cache) {
|
||||
if(cxt.window != globalThis) {
|
||||
// TODO refactor, remove cxt.window
|
||||
throw new Error('illegal state')
|
||||
}
|
||||
const result = yield* do_run(module_fns, cxt, io_cache)
|
||||
|
||||
if(result.eval_cxt.io_cache_is_replay_aborted) {
|
||||
|
||||
49
test/test.js
49
test/test.js
@@ -22,6 +22,8 @@ import {
|
||||
test_deferred_calls_state,
|
||||
print_debug_ct_node,
|
||||
command_input_async,
|
||||
patch_builtin,
|
||||
original_setTimeout,
|
||||
} from './utils.js'
|
||||
|
||||
export const tests = [
|
||||
@@ -3035,10 +3037,8 @@ const y = x()`
|
||||
}),
|
||||
|
||||
test('record io', () => {
|
||||
const random = globalThis.run_window.Math.random
|
||||
|
||||
// Patch Math.random to always return 1
|
||||
Object.assign(globalThis.run_window.Math, {random: () => 1})
|
||||
patch_builtin('random', () => 1)
|
||||
|
||||
const initial = test_initial_state(`
|
||||
const x = Math.random()
|
||||
@@ -3046,7 +3046,7 @@ const y = x()`
|
||||
|
||||
// Now call to Math.random is cached, break it to ensure it was not called
|
||||
// on next run
|
||||
Object.assign(globalThis.run_window.Math, {random: () => { throw 'fail' }})
|
||||
patch_builtin('random', () => { throw 'fail' })
|
||||
|
||||
const next = COMMANDS.input(initial, `const x = Math.random()*2`, 0).state
|
||||
assert_equal(next.value_explorer.result.value, 2)
|
||||
@@ -3055,7 +3055,7 @@ const y = x()`
|
||||
// Patch Math.random to return 2.
|
||||
// TODO The first call to Math.random() is cached with value 1, and the
|
||||
// second shoud return 2
|
||||
Object.assign(globalThis.run_window.Math, {random: () => 2})
|
||||
patch_builtin('random', () => 2)
|
||||
const replay_failed = COMMANDS.input(
|
||||
initial,
|
||||
`const x = Math.random() + Math.random()`,
|
||||
@@ -3066,14 +3066,12 @@ const y = x()`
|
||||
assert_equal(replay_failed.value_explorer.result.value, 4)
|
||||
|
||||
// Remove patch
|
||||
Object.assign(globalThis.run_window.Math, {random})
|
||||
patch_builtin('random', null)
|
||||
}),
|
||||
|
||||
test('record io cache discarded if args does not match', async () => {
|
||||
const original_fetch = globalThis.run_window.fetch
|
||||
|
||||
// Patch fetch
|
||||
Object.assign(globalThis.run_window, {fetch: async () => 'first'})
|
||||
patch_builtin('fetch', async () => 'first')
|
||||
|
||||
const initial = await test_initial_state_async(`
|
||||
console.log(await fetch('url', {method: 'GET'}))
|
||||
@@ -3081,7 +3079,7 @@ const y = x()`
|
||||
assert_equal(initial.logs.logs[0].args[0], 'first')
|
||||
|
||||
// Patch fetch again
|
||||
Object.assign(globalThis.run_window, {fetch: async () => 'second'})
|
||||
patch_builtin('fetch', async () => 'second')
|
||||
|
||||
const cache_discarded = await command_input_async(initial, `
|
||||
console.log(await fetch('url', {method: 'POST'}))
|
||||
@@ -3089,7 +3087,7 @@ const y = x()`
|
||||
assert_equal(cache_discarded.logs.logs[0].args[0], 'second')
|
||||
|
||||
// Remove patch
|
||||
Object.assign(globalThis.run_window, {fetch: original_fetch})
|
||||
patch_builtin('fetch', null)
|
||||
}),
|
||||
|
||||
test('record io fetch rejects', async () => {
|
||||
@@ -3116,8 +3114,6 @@ const y = x()`
|
||||
}),
|
||||
|
||||
test('record io preserve promise resolution order', async () => {
|
||||
const original_fetch = globalThis.run_window.fetch
|
||||
|
||||
// Generate fetch function which calls get resolved in reverse order
|
||||
const {fetch, resolve} = new Function(`
|
||||
const calls = []
|
||||
@@ -3136,7 +3132,7 @@ const y = x()`
|
||||
`)()
|
||||
|
||||
// Patch fetch
|
||||
Object.assign(globalThis.run_window, {fetch})
|
||||
patch_builtin('fetch', fetch)
|
||||
|
||||
const code = `
|
||||
await Promise.all(
|
||||
@@ -3157,7 +3153,7 @@ const y = x()`
|
||||
assert_equal(initial.logs.logs.map(l => l.args[0]), [3,2,1])
|
||||
|
||||
// Break fetch to ensure it is not get called anymore
|
||||
Object.assign(globalThis.run_window, {fetch: () => {throw 'broken'}})
|
||||
patch_builtin('fetch', () => {throw 'broken'})
|
||||
|
||||
const with_cache = await command_input_async(
|
||||
initial,
|
||||
@@ -3170,17 +3166,15 @@ const y = x()`
|
||||
assert_equal(with_cache.logs.logs.map(l => l.args[0]), [3,2,1])
|
||||
|
||||
// Remove patch
|
||||
Object.assign(globalThis.run_window, {fetch: original_fetch})
|
||||
patch_builtin('fetch', null)
|
||||
}),
|
||||
|
||||
test('record io setTimeout', async () => {
|
||||
const original_fetch = globalThis.run_window.fetch
|
||||
const setTimeout_original = globalThis.run_window.setTimeout
|
||||
|
||||
// Patch fetch to return result in 10ms
|
||||
Object.assign(globalThis.run_window, {
|
||||
fetch: () => new Promise(resolve => setTimeout_original(resolve, 10))
|
||||
})
|
||||
patch_builtin(
|
||||
'fetch',
|
||||
() => new Promise(resolve => original_setTimeout(resolve, 10))
|
||||
)
|
||||
|
||||
const code = `
|
||||
setTimeout(() => console.log('timeout'), 0)
|
||||
@@ -3193,14 +3187,14 @@ const y = x()`
|
||||
assert_equal(i.logs.logs.map(l => l.args[0]), ['timeout', 'fetch'])
|
||||
|
||||
// Break fetch to ensure it would not be called
|
||||
Object.assign(globalThis.run_window, {fetch: async () => {throw 'break'}})
|
||||
patch_builtin('fetch', async () => {throw 'break'})
|
||||
|
||||
const with_cache = await command_input_async(i, code, 0)
|
||||
|
||||
// Cache must preserve resolution order
|
||||
assert_equal(with_cache.logs.logs.map(l => l.args[0]), ['timeout', 'fetch'])
|
||||
|
||||
Object.assign(globalThis.run_window, {fetch: original_fetch})
|
||||
patch_builtin('fetch', null)
|
||||
}),
|
||||
|
||||
test('record io clear io cache', async () => {
|
||||
@@ -3235,11 +3229,12 @@ const y = x()`
|
||||
test('record io discard prev execution', () => {
|
||||
// Populate cache
|
||||
const i = test_initial_state(`Math.random(0)`)
|
||||
const rnd = i.active_calltree_node.children[0].value
|
||||
|
||||
// Run code that does not remove IO patches immediately
|
||||
// Run two versions of code in parallel
|
||||
const next = COMMANDS.input(i, `await Promise.resolve()`, 0)
|
||||
|
||||
const next2 = COMMANDS.input(i, `Math.random(1)`, 0).state
|
||||
console.log('n', next2.io_cache)
|
||||
const next_rnd = i.active_calltree_node.children[0].value
|
||||
assert_equal(rnd, next_rnd)
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -12,6 +12,36 @@ Object.assign(globalThis,
|
||||
}
|
||||
)
|
||||
|
||||
export const original_setTimeout = globalThis.setTimeout
|
||||
|
||||
export const patch_builtin = new Function(`
|
||||
// Substitute some builtin functions: fetch, setTimeout, Math.random to be
|
||||
// able to patch them in tests
|
||||
|
||||
const originals = {
|
||||
random: Math.random,
|
||||
fetch: globalThis.fetch,
|
||||
setTimeout: globalThis.setTimeout,
|
||||
}
|
||||
|
||||
const patched = {}
|
||||
|
||||
const patch = (obj, name) => {
|
||||
originals[name] = obj[name]
|
||||
obj[name] = (...args) => patched[name] == null
|
||||
? originals[name](...args)
|
||||
: patched[name](...args)
|
||||
}
|
||||
|
||||
patch(globalThis, 'fetch')
|
||||
patch(globalThis, 'setTimeout')
|
||||
patch(Math, 'random')
|
||||
|
||||
return (name, fn) => {
|
||||
patched[name] = fn
|
||||
}
|
||||
`)()
|
||||
|
||||
export const parse_modules = (entry, modules) =>
|
||||
load_modules(entry, module_name => modules[module_name])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user