mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 21:14:28 -08:00
WIP
This commit is contained in:
@@ -26,8 +26,6 @@ const io_patch = (path, use_context = false) => {
|
|||||||
|
|
||||||
const original = obj[method]
|
const original = obj[method]
|
||||||
obj[method] = function(...args) {
|
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) {
|
if(cxt.io_cache_is_replay_aborted) {
|
||||||
// Try to finish fast
|
// Try to finish fast
|
||||||
// TODO invoke callback to notify that code must be restarted?
|
// 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
|
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 = () => {
|
export const apply_io_patches = () => {
|
||||||
// TODO remove, only for dev
|
// TODO remove, only for dev
|
||||||
// TODO test open_run_window
|
// TODO test open_run_window
|
||||||
@@ -235,7 +231,6 @@ export const apply_io_patches = () => {
|
|||||||
// replaying from cache
|
// replaying from cache
|
||||||
io_patch(['clearTimeout'])
|
io_patch(['clearTimeout'])
|
||||||
|
|
||||||
|
|
||||||
// TODO patch setInterval to only cleanup all intervals on finish
|
// TODO patch setInterval to only cleanup all intervals on finish
|
||||||
|
|
||||||
const Date = cxt.window.Date
|
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) {
|
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)
|
const result = yield* do_run(module_fns, cxt, io_cache)
|
||||||
|
|
||||||
if(result.eval_cxt.io_cache_is_replay_aborted) {
|
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,
|
test_deferred_calls_state,
|
||||||
print_debug_ct_node,
|
print_debug_ct_node,
|
||||||
command_input_async,
|
command_input_async,
|
||||||
|
patch_builtin,
|
||||||
|
original_setTimeout,
|
||||||
} from './utils.js'
|
} from './utils.js'
|
||||||
|
|
||||||
export const tests = [
|
export const tests = [
|
||||||
@@ -3035,10 +3037,8 @@ const y = x()`
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
test('record io', () => {
|
test('record io', () => {
|
||||||
const random = globalThis.run_window.Math.random
|
|
||||||
|
|
||||||
// Patch Math.random to always return 1
|
// 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 initial = test_initial_state(`
|
||||||
const x = Math.random()
|
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
|
// Now call to Math.random is cached, break it to ensure it was not called
|
||||||
// on next run
|
// 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
|
const next = COMMANDS.input(initial, `const x = Math.random()*2`, 0).state
|
||||||
assert_equal(next.value_explorer.result.value, 2)
|
assert_equal(next.value_explorer.result.value, 2)
|
||||||
@@ -3055,7 +3055,7 @@ const y = x()`
|
|||||||
// Patch Math.random to return 2.
|
// Patch Math.random to return 2.
|
||||||
// TODO The first call to Math.random() is cached with value 1, and the
|
// TODO The first call to Math.random() is cached with value 1, and the
|
||||||
// second shoud return 2
|
// second shoud return 2
|
||||||
Object.assign(globalThis.run_window.Math, {random: () => 2})
|
patch_builtin('random', () => 2)
|
||||||
const replay_failed = COMMANDS.input(
|
const replay_failed = COMMANDS.input(
|
||||||
initial,
|
initial,
|
||||||
`const x = Math.random() + Math.random()`,
|
`const x = Math.random() + Math.random()`,
|
||||||
@@ -3066,14 +3066,12 @@ const y = x()`
|
|||||||
assert_equal(replay_failed.value_explorer.result.value, 4)
|
assert_equal(replay_failed.value_explorer.result.value, 4)
|
||||||
|
|
||||||
// Remove patch
|
// Remove patch
|
||||||
Object.assign(globalThis.run_window.Math, {random})
|
patch_builtin('random', null)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
test('record io cache discarded if args does not match', async () => {
|
test('record io cache discarded if args does not match', async () => {
|
||||||
const original_fetch = globalThis.run_window.fetch
|
|
||||||
|
|
||||||
// Patch fetch
|
// Patch fetch
|
||||||
Object.assign(globalThis.run_window, {fetch: async () => 'first'})
|
patch_builtin('fetch', async () => 'first')
|
||||||
|
|
||||||
const initial = await test_initial_state_async(`
|
const initial = await test_initial_state_async(`
|
||||||
console.log(await fetch('url', {method: 'GET'}))
|
console.log(await fetch('url', {method: 'GET'}))
|
||||||
@@ -3081,7 +3079,7 @@ const y = x()`
|
|||||||
assert_equal(initial.logs.logs[0].args[0], 'first')
|
assert_equal(initial.logs.logs[0].args[0], 'first')
|
||||||
|
|
||||||
// Patch fetch again
|
// Patch fetch again
|
||||||
Object.assign(globalThis.run_window, {fetch: async () => 'second'})
|
patch_builtin('fetch', async () => 'second')
|
||||||
|
|
||||||
const cache_discarded = await command_input_async(initial, `
|
const cache_discarded = await command_input_async(initial, `
|
||||||
console.log(await fetch('url', {method: 'POST'}))
|
console.log(await fetch('url', {method: 'POST'}))
|
||||||
@@ -3089,7 +3087,7 @@ const y = x()`
|
|||||||
assert_equal(cache_discarded.logs.logs[0].args[0], 'second')
|
assert_equal(cache_discarded.logs.logs[0].args[0], 'second')
|
||||||
|
|
||||||
// Remove patch
|
// Remove patch
|
||||||
Object.assign(globalThis.run_window, {fetch: original_fetch})
|
patch_builtin('fetch', null)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
test('record io fetch rejects', async () => {
|
test('record io fetch rejects', async () => {
|
||||||
@@ -3116,8 +3114,6 @@ const y = x()`
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
test('record io preserve promise resolution order', async () => {
|
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
|
// Generate fetch function which calls get resolved in reverse order
|
||||||
const {fetch, resolve} = new Function(`
|
const {fetch, resolve} = new Function(`
|
||||||
const calls = []
|
const calls = []
|
||||||
@@ -3136,7 +3132,7 @@ const y = x()`
|
|||||||
`)()
|
`)()
|
||||||
|
|
||||||
// Patch fetch
|
// Patch fetch
|
||||||
Object.assign(globalThis.run_window, {fetch})
|
patch_builtin('fetch', fetch)
|
||||||
|
|
||||||
const code = `
|
const code = `
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -3157,7 +3153,7 @@ const y = x()`
|
|||||||
assert_equal(initial.logs.logs.map(l => l.args[0]), [3,2,1])
|
assert_equal(initial.logs.logs.map(l => l.args[0]), [3,2,1])
|
||||||
|
|
||||||
// Break fetch to ensure it is not get called anymore
|
// 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(
|
const with_cache = await command_input_async(
|
||||||
initial,
|
initial,
|
||||||
@@ -3170,17 +3166,15 @@ const y = x()`
|
|||||||
assert_equal(with_cache.logs.logs.map(l => l.args[0]), [3,2,1])
|
assert_equal(with_cache.logs.logs.map(l => l.args[0]), [3,2,1])
|
||||||
|
|
||||||
// Remove patch
|
// Remove patch
|
||||||
Object.assign(globalThis.run_window, {fetch: original_fetch})
|
patch_builtin('fetch', null)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
test('record io setTimeout', async () => {
|
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
|
// Patch fetch to return result in 10ms
|
||||||
Object.assign(globalThis.run_window, {
|
patch_builtin(
|
||||||
fetch: () => new Promise(resolve => setTimeout_original(resolve, 10))
|
'fetch',
|
||||||
})
|
() => new Promise(resolve => original_setTimeout(resolve, 10))
|
||||||
|
)
|
||||||
|
|
||||||
const code = `
|
const code = `
|
||||||
setTimeout(() => console.log('timeout'), 0)
|
setTimeout(() => console.log('timeout'), 0)
|
||||||
@@ -3193,14 +3187,14 @@ const y = x()`
|
|||||||
assert_equal(i.logs.logs.map(l => l.args[0]), ['timeout', 'fetch'])
|
assert_equal(i.logs.logs.map(l => l.args[0]), ['timeout', 'fetch'])
|
||||||
|
|
||||||
// Break fetch to ensure it would not be called
|
// 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)
|
const with_cache = await command_input_async(i, code, 0)
|
||||||
|
|
||||||
// Cache must preserve resolution order
|
// Cache must preserve resolution order
|
||||||
assert_equal(with_cache.logs.logs.map(l => l.args[0]), ['timeout', 'fetch'])
|
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 () => {
|
test('record io clear io cache', async () => {
|
||||||
@@ -3235,11 +3229,12 @@ const y = x()`
|
|||||||
test('record io discard prev execution', () => {
|
test('record io discard prev execution', () => {
|
||||||
// Populate cache
|
// Populate cache
|
||||||
const i = test_initial_state(`Math.random(0)`)
|
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 next = COMMANDS.input(i, `await Promise.resolve()`, 0)
|
||||||
|
|
||||||
const next2 = COMMANDS.input(i, `Math.random(1)`, 0).state
|
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) =>
|
export const parse_modules = (entry, modules) =>
|
||||||
load_modules(entry, module_name => modules[module_name])
|
load_modules(entry, module_name => modules[module_name])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user