Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a74231563a | ||
|
|
6799d90a7d |
@@ -49,9 +49,13 @@ function ends_with(str, suffix) {
|
||||
return search(str, suffix, -length(suffix)) != null
|
||||
}
|
||||
|
||||
print("engine: loading js")
|
||||
var js = use_embed('js')
|
||||
print("engine: loading fd")
|
||||
var fd = use_embed('fd')
|
||||
print("engine: loaded fd")
|
||||
|
||||
print("engine: getting home")
|
||||
// Get the shop path from HOME environment
|
||||
var home = os.getenv('HOME') || os.getenv('USERPROFILE')
|
||||
if (!home) {
|
||||
@@ -76,34 +80,49 @@ function use_core(path) {
|
||||
if (use_cache[cache_key])
|
||||
return use_cache[cache_key];
|
||||
|
||||
print(`engine: use_core('${path}') - embed`)
|
||||
var sym = use_embed(replace(path, '/', '_'))
|
||||
|
||||
// Core scripts are in packages/core/
|
||||
var file_path = core_path + '/' + path + MOD_EXT
|
||||
|
||||
if (fd.is_file(file_path)) {
|
||||
print(`engine: use_core('${path}') - loading script`)
|
||||
var script_blob = fd.slurp(file_path)
|
||||
var script = text(script_blob)
|
||||
var mod = `(function setup_module(use){${script}})`
|
||||
var fn = mach_eval('core:' + path, mod)
|
||||
if (fn == null) {
|
||||
print(`engine: use_core('${path}') - mach_eval returned null!`)
|
||||
return null
|
||||
}
|
||||
print(`engine: use_core('${path}') - calling (fn type: ${typeof(fn)})`)
|
||||
var result = call(fn,sym, [use_core])
|
||||
print(`engine: use_core('${path}') - done`)
|
||||
use_cache[cache_key] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
print(`engine: use_core('${path}') - using embed directly`)
|
||||
use_cache[cache_key] = sym;
|
||||
return sym;
|
||||
}
|
||||
|
||||
print("engine: loading blob")
|
||||
var blob = use_core('blob')
|
||||
print("engine: loaded blob")
|
||||
|
||||
function actor() {
|
||||
|
||||
}
|
||||
|
||||
print("engine: loading actor")
|
||||
var actor_mod = use_core('actor')
|
||||
print("engine: loading wota")
|
||||
var wota = use_core('wota')
|
||||
print("engine: loading nota")
|
||||
var nota = use_core('nota')
|
||||
print("engine: loaded nota")
|
||||
|
||||
function is_actor(value) {
|
||||
return is_object(value) && value[ACTORDATA]
|
||||
@@ -191,10 +210,15 @@ function actor_die(err)
|
||||
|
||||
|
||||
|
||||
print("engine: setting up actor_die")
|
||||
actor_mod.on_exception(actor_die)
|
||||
|
||||
print("engine: setting cell args")
|
||||
print(`engine: init is ${init}`)
|
||||
_cell.args = init != null ? init : {}
|
||||
print("engine: args set")
|
||||
_cell.id = "newguy"
|
||||
print("engine: id set")
|
||||
|
||||
function create_actor(desc = {id:guid()}) {
|
||||
var actor = {}
|
||||
@@ -202,19 +226,25 @@ function create_actor(desc = {id:guid()}) {
|
||||
return actor
|
||||
}
|
||||
|
||||
print("engine: creating $_")
|
||||
var $_ = {}
|
||||
print("engine: creating self actor")
|
||||
$_.self = create_actor()
|
||||
|
||||
print("engine: setting os props")
|
||||
os.use_cache = use_cache
|
||||
os.global_shop_path = shop_path
|
||||
os.$_ = $_
|
||||
print("engine: os props set")
|
||||
|
||||
print("engine: loading shop")
|
||||
var shop = use_core('internal/shop')
|
||||
|
||||
print("engine: loaded shop, loading json")
|
||||
var json = use_core('json')
|
||||
var time = use_core('time')
|
||||
|
||||
print("engine: loading pronto")
|
||||
var pronto = use_core('pronto')
|
||||
print("engine: loaded pronto")
|
||||
var fallback = pronto.fallback
|
||||
var parallel = pronto.parallel
|
||||
var race = pronto.race
|
||||
|
||||
@@ -75,7 +75,11 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
|
||||
|
||||
JSValue js_kim_use(JSContext *js)
|
||||
{
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs));
|
||||
return mod;
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_kim_funcs, countof(js_kim_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
}
|
||||
@@ -579,7 +579,11 @@ JSValue js_os_use(JSContext *js) {
|
||||
JS_NewClassID(&js_dylib_class_id);
|
||||
JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
|
||||
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));
|
||||
return mod;
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
};
|
||||
|
||||
JSValue js_os_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs));
|
||||
return mod;
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
};
|
||||
|
||||
JSValue js_os_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs));
|
||||
return mod;
|
||||
JSGCRef mod_ref;
|
||||
JS_PushGCRef(js, &mod_ref);
|
||||
mod_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod_ref.val, js_os_funcs, countof(js_os_funcs));
|
||||
JSValue result = mod_ref.val;
|
||||
JS_PopGCRef(js, &mod_ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
114
internal/shop.cm
114
internal/shop.cm
@@ -1,5 +1,6 @@
|
||||
print("shop: starting")
|
||||
var toml = use('toml')
|
||||
|
||||
print("shop: loaded toml")
|
||||
var json = use('json')
|
||||
var fd = use('fd')
|
||||
var http = use('http')
|
||||
@@ -142,8 +143,10 @@ function package_in_shop(package) {
|
||||
|
||||
function abs_path_to_package(package_dir)
|
||||
{
|
||||
if (!fd.is_file(package_dir + '/cell.toml'))
|
||||
throw Error('Not a valid package directory (no cell.toml): ' + package_dir)
|
||||
if (!fd.is_file(package_dir + '/cell.toml')) {
|
||||
log.error('Not a valid package directory (no cell.toml): ' + package_dir)
|
||||
disrupt
|
||||
}
|
||||
|
||||
var packages_prefix = get_packages_dir() + '/'
|
||||
var core_dir = packages_prefix + core_package
|
||||
@@ -175,14 +178,15 @@ function abs_path_to_package(package_dir)
|
||||
return package_dir
|
||||
|
||||
// For local directories (e.g., linked targets), read the package name from cell.toml
|
||||
try {
|
||||
var toml_pkg = null
|
||||
var read_toml = function() {
|
||||
var content = text(fd.slurp(package_dir + '/cell.toml'))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.package)
|
||||
return cfg.package
|
||||
} catch (e) {
|
||||
// Fall through
|
||||
}
|
||||
toml_pkg = cfg.package
|
||||
} disruption {}
|
||||
read_toml()
|
||||
if (toml_pkg) return toml_pkg
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -299,12 +303,14 @@ Shop.resolve_package_info = function(pkg) {
|
||||
|
||||
// Verify if a package name is valid and return status
|
||||
Shop.verify_package_name = function(pkg) {
|
||||
if (!pkg) throw Error("Empty package name")
|
||||
if (pkg == 'local') throw Error("local is not a valid package name")
|
||||
if (pkg == 'core') throw Error("core is not a valid package name")
|
||||
if (!pkg) { log.error("Empty package name"); disrupt }
|
||||
if (pkg == 'local') { log.error("local is not a valid package name"); disrupt }
|
||||
if (pkg == 'core') { log.error("core is not a valid package name"); disrupt }
|
||||
|
||||
if (search(pkg, '://') != null)
|
||||
throw Error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
|
||||
if (search(pkg, '://') != null) {
|
||||
log.error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
|
||||
disrupt
|
||||
}
|
||||
}
|
||||
|
||||
// Convert module package to download URL
|
||||
@@ -441,7 +447,7 @@ ${script}
|
||||
// Resolve module function, hashing it in the process
|
||||
// path is the exact path to the script file
|
||||
function resolve_mod_fn(path, pkg) {
|
||||
if (!fd.is_file(path)) throw Error(`path ${path} is not a file`)
|
||||
if (!fd.is_file(path)) { log.error(`path ${path} is not a file`); disrupt }
|
||||
|
||||
var file_info = Shop.file_info(path)
|
||||
var file_pkg = file_info.package
|
||||
@@ -576,32 +582,38 @@ Shop.open_package_dylib = function(pkg) {
|
||||
|
||||
var toml_path = pkg_dir + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
try {
|
||||
var read_toml_disrupted = false
|
||||
var do_read_toml = function() {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias, i) {
|
||||
var dep_pkg = cfg.dependencies[alias]
|
||||
try {
|
||||
var open_dep = function() {
|
||||
Shop.open_package_dylib(dep_pkg)
|
||||
} catch (dep_e) {
|
||||
// Dependency dylib load failed, continue with others
|
||||
}
|
||||
} disruption {}
|
||||
open_dep()
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Error reading toml, continue
|
||||
} disruption {
|
||||
read_toml_disrupted = true
|
||||
}
|
||||
do_read_toml()
|
||||
}
|
||||
|
||||
var dl_path = get_lib_path(pkg)
|
||||
if (fd.is_file(dl_path)) {
|
||||
if (!open_dls[dl_path]) {
|
||||
try {
|
||||
var open_disrupted = false
|
||||
var do_open = function() {
|
||||
open_dls[dl_path] = os.dylib_open(dl_path)
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
open_disrupted = true
|
||||
}
|
||||
do_open()
|
||||
if (open_disrupted) {
|
||||
dylib_visited[pkg] = false
|
||||
throw e
|
||||
disrupt
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -836,14 +848,14 @@ function execute_module(info)
|
||||
// C only
|
||||
used = call_c_module(c_resolve)
|
||||
} else {
|
||||
throw Error(`Module ${info.path} could not be found`)
|
||||
log.error(`Module ${info.path} could not be found`); disrupt
|
||||
}
|
||||
|
||||
// if (is_function(used))
|
||||
// throw Error('C module loader returned a function; did you forget to call it?')
|
||||
|
||||
if (!used)
|
||||
throw Error(`Module ${info} returned null`)
|
||||
log.error(`Module ${info} returned null`); disrupt
|
||||
|
||||
// stone(used)
|
||||
return used
|
||||
@@ -852,16 +864,20 @@ function execute_module(info)
|
||||
function get_module(path, package_context) {
|
||||
var info = resolve_module_info(path, package_context)
|
||||
|
||||
if (!info)
|
||||
throw Error(`Module ${path} could not be found in ${package_context}`)
|
||||
if (!info) {
|
||||
log.error(`Module ${path} could not be found in ${package_context}`)
|
||||
disrupt
|
||||
}
|
||||
|
||||
return execute_module(info)
|
||||
}
|
||||
|
||||
Shop.use = function use(path, package_context) {
|
||||
var info = resolve_module_info(path, package_context)
|
||||
if (!info)
|
||||
throw Error(`Module ${path} could not be found in ${package_context}`)
|
||||
if (!info) {
|
||||
log.error(`Module ${path} could not be found in ${package_context}`)
|
||||
disrupt
|
||||
}
|
||||
|
||||
if (use_cache[info.cache_key])
|
||||
return use_cache[info.cache_key]
|
||||
@@ -889,13 +905,20 @@ function fetch_remote_hash(pkg) {
|
||||
if (!api_url) return null
|
||||
|
||||
|
||||
try {
|
||||
var result = null
|
||||
var fetch_disrupted = false
|
||||
var do_fetch = function() {
|
||||
var resp = http.fetch(api_url)
|
||||
return Shop.extract_commit_hash(pkg, text(resp))
|
||||
} catch (e) {
|
||||
result = Shop.extract_commit_hash(pkg, text(resp))
|
||||
} disruption {
|
||||
fetch_disrupted = true
|
||||
}
|
||||
do_fetch()
|
||||
if (fetch_disrupted) {
|
||||
log.console("Warning: Could not check for updates for " + pkg)
|
||||
return null
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Download a zip for a package at a specific commit and cache it
|
||||
@@ -909,14 +932,20 @@ function download_zip(pkg, commit_hash) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
var zip_blob = http.fetch(download_url)
|
||||
var zip_blob = null
|
||||
var dl_disrupted = false
|
||||
var do_download = function() {
|
||||
zip_blob = http.fetch(download_url)
|
||||
fd.slurpwrite(cache_path, zip_blob)
|
||||
return zip_blob
|
||||
} catch (e) {
|
||||
log.error("Download failed for " + pkg + ": " + e)
|
||||
} disruption {
|
||||
dl_disrupted = true
|
||||
}
|
||||
do_download()
|
||||
if (dl_disrupted) {
|
||||
log.error("Download failed for " + pkg)
|
||||
return null
|
||||
}
|
||||
return zip_blob
|
||||
}
|
||||
|
||||
// Get zip from cache, returns null if not cached
|
||||
@@ -1027,8 +1056,7 @@ Shop.extract = function(pkg) {
|
||||
|
||||
var zip_blob = get_package_zip(pkg)
|
||||
|
||||
if (!zip_blob)
|
||||
throw Error("No zip blob available for " + pkg)
|
||||
if (!zip_blob) { log.error("No zip blob available for " + pkg); disrupt }
|
||||
|
||||
// Extract zip for remote package
|
||||
install_zip(zip_blob, target_dir)
|
||||
@@ -1113,7 +1141,7 @@ Shop.update = function(pkg) {
|
||||
|
||||
function install_zip(zip_blob, target_dir) {
|
||||
var zip = miniz.read(zip_blob)
|
||||
if (!zip) throw Error("Failed to read zip archive")
|
||||
if (!zip) { log.error("Failed to read zip archive"); disrupt }
|
||||
|
||||
if (fd.is_link(target_dir)) fd.unlink(target_dir)
|
||||
if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1)
|
||||
@@ -1165,14 +1193,14 @@ Shop.get = function(pkg) {
|
||||
if (!lock[pkg]) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
if (!info) {
|
||||
throw Error("Invalid package: " + pkg)
|
||||
log.error("Invalid package: " + pkg); disrupt
|
||||
}
|
||||
|
||||
var commit = null
|
||||
if (info != 'local') {
|
||||
commit = fetch_remote_hash(pkg)
|
||||
if (!commit) {
|
||||
throw Error("Could not resolve commit for " + pkg)
|
||||
log.error("Could not resolve commit for " + pkg); disrupt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,13 @@ function is_valid_package(dir) {
|
||||
// Get current package name from cell.toml or null
|
||||
function get_current_package_name() {
|
||||
if (!is_valid_package('.')) return null
|
||||
try {
|
||||
var pkg_name = 'local'
|
||||
var do_load = function() {
|
||||
var config = pkg.load_config(null)
|
||||
return config.package || 'local'
|
||||
} catch (e) {
|
||||
return 'local'
|
||||
}
|
||||
if (config.package) pkg_name = config.package
|
||||
} disruption {}
|
||||
do_load()
|
||||
return pkg_name
|
||||
}
|
||||
|
||||
// Get the directory for a package
|
||||
|
||||
65
link.cm
65
link.cm
@@ -66,12 +66,17 @@ Link.load = function() {
|
||||
return link_cache
|
||||
}
|
||||
|
||||
try {
|
||||
var load_disrupted = false
|
||||
var do_load = function() {
|
||||
var content = text(fd.slurp(path))
|
||||
var cfg = toml.decode(content)
|
||||
link_cache = cfg.links || {}
|
||||
} catch (e) {
|
||||
log.console("Warning: Failed to load link.toml: " + e)
|
||||
} disruption {
|
||||
load_disrupted = true
|
||||
}
|
||||
do_load()
|
||||
if (load_disrupted) {
|
||||
log.console("Warning: Failed to load link.toml")
|
||||
link_cache = {}
|
||||
}
|
||||
return link_cache
|
||||
@@ -90,14 +95,16 @@ Link.add = function(canonical, target, shop) {
|
||||
// Validate canonical package exists in shop
|
||||
var lock = shop.load_lock()
|
||||
if (!lock[canonical]) {
|
||||
throw Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
|
||||
log.error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
|
||||
disrupt
|
||||
}
|
||||
|
||||
// Validate target is a valid package
|
||||
if (starts_with(target, '/')) {
|
||||
// Local path - must have cell.toml
|
||||
if (!fd.is_file(target + '/cell.toml')) {
|
||||
throw Error('Target ' + target + ' is not a valid package (no cell.toml)')
|
||||
log.error('Target ' + target + ' is not a valid package (no cell.toml)')
|
||||
disrupt
|
||||
}
|
||||
} else {
|
||||
// Remote package target - ensure it's installed
|
||||
@@ -116,7 +123,8 @@ Link.add = function(canonical, target, shop) {
|
||||
var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target)
|
||||
var toml_path = target_path + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
try {
|
||||
var read_deps_disrupted = false
|
||||
var do_read_deps = function() {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
@@ -128,16 +136,24 @@ Link.add = function(canonical, target, shop) {
|
||||
return
|
||||
}
|
||||
// Install the dependency if not already in shop
|
||||
try {
|
||||
var install_disrupted = false
|
||||
var do_install = function() {
|
||||
shop.get(dep_locator)
|
||||
shop.extract(dep_locator)
|
||||
} catch (e) {
|
||||
log.console(` Warning: Could not install dependency ${dep_locator}: ${e.message}`)
|
||||
log.error(e)
|
||||
} disruption {
|
||||
install_disrupted = true
|
||||
}
|
||||
do_install()
|
||||
if (install_disrupted) {
|
||||
log.console(` Warning: Could not install dependency ${dep_locator}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
read_deps_disrupted = true
|
||||
}
|
||||
do_read_deps()
|
||||
if (read_deps_disrupted) {
|
||||
log.console(` Warning: Could not read dependencies from ${toml_path}`)
|
||||
}
|
||||
}
|
||||
@@ -218,7 +234,9 @@ Link.sync_all = function(shop) {
|
||||
|
||||
arrfor(array(links), function(canonical) {
|
||||
var target = links[canonical]
|
||||
try {
|
||||
var sync_disrupted = false
|
||||
var sync_error_msg = ""
|
||||
var do_sync = function() {
|
||||
// Validate target exists
|
||||
var link_target = resolve_link_target(target)
|
||||
if (!fd.is_dir(link_target)) {
|
||||
@@ -234,7 +252,8 @@ Link.sync_all = function(shop) {
|
||||
|
||||
// Install dependencies of the linked package
|
||||
var toml_path = link_target + '/cell.toml'
|
||||
try {
|
||||
var read_deps_disrupted = false
|
||||
var do_read_deps = function() {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
@@ -245,21 +264,25 @@ Link.sync_all = function(shop) {
|
||||
return
|
||||
}
|
||||
// Install the dependency if not already in shop
|
||||
try {
|
||||
var install_dep = function() {
|
||||
shop.get(dep_locator)
|
||||
shop.extract(dep_locator)
|
||||
} catch (e) {
|
||||
// Silently continue - dependency may already be installed
|
||||
}
|
||||
} disruption {}
|
||||
install_dep()
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Could not read dependencies - continue anyway
|
||||
} disruption {
|
||||
read_deps_disrupted = true
|
||||
}
|
||||
do_read_deps()
|
||||
|
||||
count++
|
||||
} catch (e) {
|
||||
push(errors, canonical + ': ' + e.message)
|
||||
} disruption {
|
||||
sync_disrupted = true
|
||||
}
|
||||
do_sync()
|
||||
if (sync_disrupted) {
|
||||
push(errors, canonical + ': sync failed')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
16
package.cm
16
package.cm
@@ -51,7 +51,8 @@ package.load_config = function(name)
|
||||
return config_cache[config_path]
|
||||
|
||||
if (!fd.is_file(config_path)) {
|
||||
throw Error(`${config_path} does not exist`)
|
||||
log.error(`${config_path} does not exist`)
|
||||
disrupt
|
||||
}
|
||||
|
||||
var content = text(fd.slurp(config_path))
|
||||
@@ -158,19 +159,20 @@ package.split_alias = function(name, path)
|
||||
var parts = array(path, '/')
|
||||
var first_part = parts[0]
|
||||
|
||||
try {
|
||||
var split_result = null
|
||||
var do_split = function() {
|
||||
var config = package.load_config(name)
|
||||
if (!config) return null
|
||||
if (!config) return
|
||||
|
||||
var deps = config.dependencies
|
||||
if (deps && deps[first_part]) {
|
||||
var dep_locator = deps[first_part]
|
||||
var remaining_path = text(array(parts, 1), '/')
|
||||
return { package: dep_locator, path: remaining_path }
|
||||
}
|
||||
} catch (e) {
|
||||
// Config doesn't exist or couldn't be loaded
|
||||
split_result = { package: dep_locator, path: remaining_path }
|
||||
}
|
||||
} disruption {}
|
||||
do_split()
|
||||
if (split_result) return split_result
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
95
pronto.cm
95
pronto.cm
@@ -3,6 +3,8 @@
|
||||
// Based on Douglas Crockford's parseq, adapted for Cell.
|
||||
// Time is in seconds.
|
||||
|
||||
function safe_call(fn, arg) { fn(arg) } disruption {}
|
||||
|
||||
function make_reason(factory, excuse, evidence) {
|
||||
def reason = Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`)
|
||||
reason.evidence = evidence
|
||||
@@ -15,12 +17,12 @@ function is_requestor(fn) {
|
||||
|
||||
function check_requestors(list, factory) {
|
||||
if (!is_array(list) || some(list, r => !is_requestor(r)))
|
||||
throw make_reason(factory, 'Bad requestor array.', list)
|
||||
disrupt
|
||||
}
|
||||
|
||||
function check_callback(cb, factory) {
|
||||
if (!is_function(cb) || length(cb) != 2)
|
||||
throw make_reason(factory, 'Not a callback.', cb)
|
||||
disrupt
|
||||
}
|
||||
|
||||
// fallback(requestor_array)
|
||||
@@ -28,7 +30,7 @@ function check_callback(cb, factory) {
|
||||
function fallback(requestor_array) {
|
||||
def factory = 'fallback'
|
||||
if (!is_array(requestor_array) || length(requestor_array) == 0)
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
disrupt
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
return function fallback_requestor(callback, value) {
|
||||
@@ -40,7 +42,7 @@ function fallback(requestor_array) {
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
if (current_cancel) {
|
||||
try { current_cancel(reason) } catch (_) {}
|
||||
safe_call(current_cancel, reason)
|
||||
current_cancel = null
|
||||
}
|
||||
}
|
||||
@@ -55,7 +57,8 @@ function fallback(requestor_array) {
|
||||
def requestor = requestor_array[index]
|
||||
index += 1
|
||||
|
||||
try {
|
||||
var requestor_disrupted = false
|
||||
var start_requestor = function() {
|
||||
current_cancel = requestor(function(val, reason) {
|
||||
if (cancelled) return
|
||||
current_cancel = null
|
||||
@@ -65,7 +68,11 @@ function fallback(requestor_array) {
|
||||
try_next()
|
||||
}
|
||||
}, value)
|
||||
} catch (ex) {
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
start_requestor()
|
||||
if (requestor_disrupted) {
|
||||
try_next()
|
||||
}
|
||||
}
|
||||
@@ -80,7 +87,7 @@ function fallback(requestor_array) {
|
||||
function parallel(requestor_array, throttle, need) {
|
||||
def factory = 'parallel'
|
||||
if (!is_array(requestor_array))
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
disrupt
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
def length = length(requestor_array)
|
||||
@@ -89,10 +96,10 @@ function parallel(requestor_array, throttle, need) {
|
||||
|
||||
if (need == null) need = length
|
||||
if (!is_number(need) || need < 0 || need > length)
|
||||
throw make_reason(factory, 'Bad need.', need)
|
||||
disrupt
|
||||
|
||||
if (throttle != null && (!is_number(throttle) || throttle < 1))
|
||||
throw make_reason(factory, 'Bad throttle.', throttle)
|
||||
disrupt
|
||||
|
||||
return function parallel_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
@@ -107,7 +114,8 @@ function parallel(requestor_array, throttle, need) {
|
||||
if (finished) return
|
||||
finished = true
|
||||
arrfor(cancel_list, c => {
|
||||
try { if (is_function(c)) c(reason) } catch (_) {}
|
||||
var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
|
||||
do_cancel()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,7 +125,9 @@ function parallel(requestor_array, throttle, need) {
|
||||
next_index += 1
|
||||
def requestor = requestor_array[idx]
|
||||
|
||||
try {
|
||||
var requestor_disrupted = false
|
||||
var requestor_ex = null
|
||||
var run_requestor = function() {
|
||||
cancel_list[idx] = requestor(function(val, reason) {
|
||||
if (finished) return
|
||||
cancel_list[idx] = null
|
||||
@@ -142,11 +152,15 @@ function parallel(requestor_array, throttle, need) {
|
||||
|
||||
start_one()
|
||||
}, value)
|
||||
} catch (ex) {
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
run_requestor()
|
||||
if (requestor_disrupted) {
|
||||
failures += 1
|
||||
if (failures > length - need) {
|
||||
cancel(ex)
|
||||
callback(null, ex)
|
||||
cancel(requestor_ex)
|
||||
callback(null, requestor_ex)
|
||||
return
|
||||
}
|
||||
start_one()
|
||||
@@ -166,16 +180,16 @@ function parallel(requestor_array, throttle, need) {
|
||||
function race(requestor_array, throttle, need) {
|
||||
def factory = 'race'
|
||||
if (!is_array(requestor_array) || length(requestor_array) == 0)
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
disrupt
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
def length = length(requestor_array)
|
||||
if (need == null) need = 1
|
||||
if (!is_number(need) || need < 1 || need > length)
|
||||
throw make_reason(factory, 'Bad need.', need)
|
||||
disrupt
|
||||
|
||||
if (throttle != null && (!is_number(throttle) || throttle < 1))
|
||||
throw make_reason(factory, 'Bad throttle.', throttle)
|
||||
disrupt
|
||||
|
||||
return function race_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
@@ -190,7 +204,8 @@ function race(requestor_array, throttle, need) {
|
||||
if (finished) return
|
||||
finished = true
|
||||
arrfor(cancel_list, c => {
|
||||
try { if (is_function(c)) c(reason) } catch (_) {}
|
||||
var do_cancel = function() { if (is_function(c)) c(reason) } disruption {}
|
||||
do_cancel()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -200,7 +215,9 @@ function race(requestor_array, throttle, need) {
|
||||
next_index += 1
|
||||
def requestor = requestor_array[idx]
|
||||
|
||||
try {
|
||||
var requestor_disrupted = false
|
||||
var requestor_ex = null
|
||||
var run_requestor = function() {
|
||||
cancel_list[idx] = requestor(function(val, reason) {
|
||||
if (finished) return
|
||||
cancel_list[idx] = null
|
||||
@@ -228,11 +245,15 @@ function race(requestor_array, throttle, need) {
|
||||
|
||||
start_one()
|
||||
}, value)
|
||||
} catch (ex) {
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
run_requestor()
|
||||
if (requestor_disrupted) {
|
||||
failures += 1
|
||||
if (failures > length - need) {
|
||||
cancel(ex)
|
||||
callback(null, ex)
|
||||
cancel(requestor_ex)
|
||||
callback(null, requestor_ex)
|
||||
return
|
||||
}
|
||||
start_one()
|
||||
@@ -251,7 +272,7 @@ function race(requestor_array, throttle, need) {
|
||||
function sequence(requestor_array) {
|
||||
def factory = 'sequence'
|
||||
if (!is_array(requestor_array))
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
disrupt
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
if (length(requestor_array) == 0)
|
||||
@@ -266,7 +287,7 @@ function sequence(requestor_array) {
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
if (current_cancel) {
|
||||
try { current_cancel(reason) } catch (_) {}
|
||||
safe_call(current_cancel, reason)
|
||||
current_cancel = null
|
||||
}
|
||||
}
|
||||
@@ -281,7 +302,9 @@ function sequence(requestor_array) {
|
||||
def requestor = requestor_array[index]
|
||||
index += 1
|
||||
|
||||
try {
|
||||
var requestor_disrupted = false
|
||||
var requestor_ex = null
|
||||
var run_requestor = function() {
|
||||
current_cancel = requestor(function(result, reason) {
|
||||
if (cancelled) return
|
||||
current_cancel = null
|
||||
@@ -291,8 +314,12 @@ function sequence(requestor_array) {
|
||||
run_next(result)
|
||||
}
|
||||
}, val)
|
||||
} catch (ex) {
|
||||
callback(null, ex)
|
||||
} disruption {
|
||||
requestor_disrupted = true
|
||||
}
|
||||
run_requestor()
|
||||
if (requestor_disrupted) {
|
||||
callback(null, requestor_ex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,15 +333,21 @@ function sequence(requestor_array) {
|
||||
function requestorize(unary) {
|
||||
def factory = 'requestorize'
|
||||
if (!is_function(unary))
|
||||
throw make_reason(factory, 'Not a function.', unary)
|
||||
disrupt
|
||||
|
||||
return function requestorized(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
try {
|
||||
var call_disrupted = false
|
||||
var call_ex = null
|
||||
var do_call = function() {
|
||||
def result = unary(value)
|
||||
callback(result == null ? true : result)
|
||||
} catch (ex) {
|
||||
callback(null, ex)
|
||||
} disruption {
|
||||
call_disrupted = true
|
||||
}
|
||||
do_call()
|
||||
if (call_disrupted) {
|
||||
callback(null, call_ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,31 +210,54 @@ void script_startup(cell_rt *prt)
|
||||
return;
|
||||
}
|
||||
|
||||
// Create hidden environment
|
||||
JSValue hidden_env = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
|
||||
// Create hidden environment (GC-protected to survive allocations)
|
||||
JSGCRef env_ref;
|
||||
JS_PushGCRef(js, &env_ref);
|
||||
env_ref.val = JS_NewObject(js);
|
||||
|
||||
// Create modules with GC rooting
|
||||
JSGCRef os_ref, json_ref, nota_ref, wota_ref;
|
||||
JS_PushGCRef(js, &os_ref);
|
||||
JS_PushGCRef(js, &json_ref);
|
||||
JS_PushGCRef(js, ¬a_ref);
|
||||
JS_PushGCRef(js, &wota_ref);
|
||||
|
||||
os_ref.val = js_os_use(js);
|
||||
json_ref.val = js_json_use(js);
|
||||
nota_ref.val = js_nota_use(js);
|
||||
wota_ref.val = js_wota_use(js);
|
||||
|
||||
// Set properties on env (SetPropertyStr internally roots its args)
|
||||
JS_SetPropertyStr(js, env_ref.val, "os", os_ref.val);
|
||||
JS_SetPropertyStr(js, env_ref.val, "json", json_ref.val);
|
||||
JS_SetPropertyStr(js, env_ref.val, "nota", nota_ref.val);
|
||||
JS_SetPropertyStr(js, env_ref.val, "wota", wota_ref.val);
|
||||
|
||||
JS_PopGCRef(js, &wota_ref);
|
||||
JS_PopGCRef(js, ¬a_ref);
|
||||
JS_PopGCRef(js, &json_ref);
|
||||
JS_PopGCRef(js, &os_ref);
|
||||
|
||||
crt->actor_sym_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
|
||||
|
||||
// Always set init (even if null)
|
||||
if (crt->init_wota) {
|
||||
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota));
|
||||
JSValue init_val = wota2value(js, crt->init_wota);
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", init_val);
|
||||
free(crt->init_wota);
|
||||
crt->init_wota = NULL;
|
||||
} else {
|
||||
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL);
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
|
||||
}
|
||||
|
||||
if (core_path) {
|
||||
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
|
||||
JSValue path_val = JS_NewString(js, core_path);
|
||||
JS_SetPropertyStr(js, env_ref.val, "core_path", path_val);
|
||||
}
|
||||
|
||||
// Stone the environment
|
||||
hidden_env = JS_Stone(js, hidden_env);
|
||||
JSValue hidden_env = JS_Stone(js, env_ref.val);
|
||||
JS_PopGCRef(js, &env_ref);
|
||||
|
||||
// Run through MACH VM
|
||||
crt->state = ACTOR_RUNNING;
|
||||
|
||||
90
test.ce
90
test.ce
@@ -29,12 +29,13 @@ function is_valid_package(dir) {
|
||||
// Get current package name from cell.toml or null
|
||||
function get_current_package_name() {
|
||||
if (!is_valid_package('.')) return null
|
||||
try {
|
||||
var pkg_name = 'local'
|
||||
var do_load = function() {
|
||||
var config = pkg.load_config(null)
|
||||
return config.package || 'local'
|
||||
} catch (e) {
|
||||
return 'local'
|
||||
}
|
||||
if (config.package) pkg_name = config.package
|
||||
} disruption {}
|
||||
do_load()
|
||||
return pkg_name
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
@@ -229,21 +230,48 @@ function spawn_actor_test(test_info) {
|
||||
actor: null
|
||||
}
|
||||
|
||||
try {
|
||||
var spawn_disrupted = false
|
||||
var do_spawn = function() {
|
||||
// Spawn the actor test - it should send back results
|
||||
var actor_path = text(test_info.path, 0, -3) // remove .ce
|
||||
entry.actor = $start(actor_path)
|
||||
push(pending_actor_tests, entry)
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
spawn_disrupted = true
|
||||
}
|
||||
do_spawn()
|
||||
if (spawn_disrupted) {
|
||||
entry.status = "failed"
|
||||
entry.error = { message: `Failed to spawn actor: ${e}` }
|
||||
entry.error = { message: `Failed to spawn actor: ${test_name}` }
|
||||
entry.duration_ns = 0
|
||||
push(actor_test_results, entry)
|
||||
log.console(` FAIL ${test_name}: `)
|
||||
log.error(e)
|
||||
log.console(` FAIL ${test_name}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Test runner with disruption support
|
||||
var test_passed = true
|
||||
var test_error_msg = ""
|
||||
var test_error_stack = ""
|
||||
|
||||
var run_test = function(fn) {
|
||||
test_passed = true
|
||||
test_error_msg = ""
|
||||
test_error_stack = ""
|
||||
var ret = fn()
|
||||
if (is_text(ret)) {
|
||||
test_passed = false
|
||||
test_error_msg = ret
|
||||
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
|
||||
test_passed = false
|
||||
test_error_msg = ret.message || text(ret)
|
||||
if (ret.stack) test_error_stack = ret.stack
|
||||
}
|
||||
} disruption {
|
||||
test_passed = false
|
||||
if (test_error_msg == "") test_error_msg = "test disrupted"
|
||||
}
|
||||
|
||||
function run_tests(package_name, specific_test) {
|
||||
var prefix = get_pkg_dir(package_name)
|
||||
var tests_dir = prefix + '/tests'
|
||||
@@ -293,7 +321,9 @@ function run_tests(package_name, specific_test) {
|
||||
failed: 0
|
||||
}
|
||||
|
||||
try {
|
||||
var load_disrupted = false
|
||||
var load_error_msg = ""
|
||||
var do_load = function() {
|
||||
var test_mod
|
||||
// For local packages (null), use the current directory as package context
|
||||
var use_pkg = package_name ? package_name : fd.realpath('.')
|
||||
@@ -322,34 +352,23 @@ function run_tests(package_name, specific_test) {
|
||||
}
|
||||
|
||||
var start_time = time.number()
|
||||
try {
|
||||
var ret = t.fn()
|
||||
|
||||
if (is_text(ret)) {
|
||||
throw Error(ret)
|
||||
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
|
||||
throw ret
|
||||
}
|
||||
run_test(t.fn)
|
||||
|
||||
if (test_passed) {
|
||||
test_entry.status = "passed"
|
||||
log.console(` PASS ${t.name}`)
|
||||
pkg_result.passed++
|
||||
file_result.passed++
|
||||
} catch (e) {
|
||||
} else {
|
||||
test_entry.status = "failed"
|
||||
test_entry.error = {
|
||||
message: e,
|
||||
stack: e.stack || ""
|
||||
message: test_error_msg
|
||||
}
|
||||
if (e.name) test_entry.error.name = e.name
|
||||
if (test_error_stack) test_entry.error.stack = test_error_stack
|
||||
|
||||
if (is_object(e) && e.message) {
|
||||
test_entry.error.message = e.message
|
||||
}
|
||||
|
||||
log.console(` FAIL ${t.name} ${test_entry.error.message}`)
|
||||
if (test_entry.error.stack) {
|
||||
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
|
||||
log.console(` FAIL ${t.name} ${test_error_msg}`)
|
||||
if (test_error_stack) {
|
||||
log.console(` ${text(array(test_error_stack, '\n'), '\n ')}`)
|
||||
}
|
||||
|
||||
pkg_result.failed++
|
||||
@@ -365,15 +384,18 @@ function run_tests(package_name, specific_test) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
log.console(` Error loading ${f}: ${e}`)
|
||||
} disruption {
|
||||
load_disrupted = true
|
||||
}
|
||||
do_load()
|
||||
if (load_disrupted) {
|
||||
log.console(` Error loading ${f}`)
|
||||
var test_entry = {
|
||||
package: pkg_result.package,
|
||||
test: "load_module",
|
||||
status: "failed",
|
||||
duration_ns: 0,
|
||||
error: { message: `Error loading module: ${e}` }
|
||||
error: { message: `Error loading module: ${f}` }
|
||||
}
|
||||
push(file_result.tests, test_entry)
|
||||
pkg_result.failed++
|
||||
|
||||
242
tests/blob.cm
242
tests/blob.cm
@@ -4,25 +4,32 @@ var os = use('os');
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw Error(message || "Assertion failed");
|
||||
return message || "Assertion failed"
|
||||
}
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, message) {
|
||||
if (actual != expected) {
|
||||
throw Error(message || "Expected " + expected + ", got " + actual);
|
||||
return message || "Expected " + expected + ", got " + actual
|
||||
}
|
||||
}
|
||||
|
||||
function should_disrupt(fn) {
|
||||
var caught = false
|
||||
var wrapper = function() { fn() } disruption { caught = true }
|
||||
wrapper()
|
||||
return caught
|
||||
}
|
||||
|
||||
return {
|
||||
test_create_empty_blob: function() {
|
||||
var b = Blob();
|
||||
assertEqual(length(b), 0, "Empty blob should have length 0");
|
||||
return assertEqual(length(b), 0, "Empty blob should have length 0")
|
||||
},
|
||||
|
||||
test_create_blob_with_capacity: function() {
|
||||
var b = Blob(100);
|
||||
assertEqual(length(b), 0, "New blob with capacity should still have length 0");
|
||||
return assertEqual(length(b), 0, "New blob with capacity should still have length 0")
|
||||
},
|
||||
|
||||
test_write_and_read_single_bit: function() {
|
||||
@@ -31,13 +38,17 @@ return {
|
||||
b.write_bit(false);
|
||||
b.write_bit(1);
|
||||
b.write_bit(0);
|
||||
assertEqual(length(b), 4, "Should have 4 bits after writing");
|
||||
var r = assertEqual(length(b), 4, "Should have 4 bits after writing")
|
||||
if (r) return r
|
||||
|
||||
stone(b);
|
||||
assertEqual(b.read_logical(0), true, "First bit should be true");
|
||||
assertEqual(b.read_logical(1), false, "Second bit should be false");
|
||||
assertEqual(b.read_logical(2), true, "Third bit should be true (1)");
|
||||
assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)");
|
||||
r = assertEqual(b.read_logical(0), true, "First bit should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(1), false, "Second bit should be false")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(2), true, "Third bit should be true (1)")
|
||||
if (r) return r
|
||||
return assertEqual(b.read_logical(3), false, "Fourth bit should be false (0)")
|
||||
},
|
||||
|
||||
test_out_of_range_read_throws_error: function() {
|
||||
@@ -45,21 +56,11 @@ return {
|
||||
b.write_bit(true);
|
||||
stone(b);
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.read_logical(100);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Out of range read should throw");
|
||||
if (!should_disrupt(function() { b.read_logical(100) }))
|
||||
return "Out of range read should disrupt"
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_logical(-1);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Negative index read should throw");
|
||||
if (!should_disrupt(function() { b.read_logical(-1) }))
|
||||
return "Negative index read should disrupt"
|
||||
},
|
||||
|
||||
test_write_and_read_numbers: function() {
|
||||
@@ -70,10 +71,13 @@ return {
|
||||
b.write_number(1e20);
|
||||
stone(b);
|
||||
|
||||
assertEqual(b.read_number(0), 3.14159, "First number should match");
|
||||
assertEqual(b.read_number(64), -42, "Second number should match");
|
||||
assertEqual(b.read_number(128), 0, "Third number should match");
|
||||
assertEqual(b.read_number(192), 1e20, "Fourth number should match");
|
||||
var r = assertEqual(b.read_number(0), 3.14159, "First number should match")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_number(64), -42, "Second number should match")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_number(128), 0, "Third number should match")
|
||||
if (r) return r
|
||||
return assertEqual(b.read_number(192), 1e20, "Fourth number should match")
|
||||
},
|
||||
|
||||
test_write_and_read_text: function() {
|
||||
@@ -83,7 +87,7 @@ return {
|
||||
b.write_text("🎉");
|
||||
stone(b);
|
||||
|
||||
assertEqual(b.read_text(0), "Hello", "First text should match");
|
||||
return assertEqual(b.read_text(0), "Hello", "First text should match")
|
||||
},
|
||||
|
||||
test_write_and_read_blobs: function() {
|
||||
@@ -95,13 +99,17 @@ return {
|
||||
var b2 = Blob(10);
|
||||
b2.write_blob(b1);
|
||||
b2.write_bit(false);
|
||||
assertEqual(length(b2), 4, "Combined blob should have 4 bits");
|
||||
var r = assertEqual(length(b2), 4, "Combined blob should have 4 bits")
|
||||
if (r) return r
|
||||
|
||||
stone(b2);
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(1), false);
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
assertEqual(b2.read_logical(3), false);
|
||||
r = assertEqual(b2.read_logical(0), true)
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(1), false)
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(2), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(3), false)
|
||||
},
|
||||
|
||||
test_blob_copy_constructor: function() {
|
||||
@@ -114,9 +122,11 @@ return {
|
||||
|
||||
var b2 = Blob(b1);
|
||||
stone(b2);
|
||||
assertEqual(length(b2), 4, "Copied blob should have same length");
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(3), true);
|
||||
var r = assertEqual(length(b2), 4, "Copied blob should have same length")
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(0), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(3), true)
|
||||
},
|
||||
|
||||
test_blob_partial_copy_constructor: function() {
|
||||
@@ -128,9 +138,11 @@ return {
|
||||
|
||||
var b2 = Blob(b1, 2, 7);
|
||||
stone(b2);
|
||||
assertEqual(length(b2), 5, "Partial copy should have 5 bits");
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
var r = assertEqual(length(b2), 5, "Partial copy should have 5 bits")
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(0), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(2), true)
|
||||
},
|
||||
|
||||
test_create_blob_with_fill: function() {
|
||||
@@ -141,8 +153,10 @@ return {
|
||||
stone(b2);
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true");
|
||||
assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false");
|
||||
var r = assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b2.read_logical(i), false, "Bit " + i + " should be false")
|
||||
if (r) return r
|
||||
}
|
||||
},
|
||||
|
||||
@@ -156,7 +170,8 @@ return {
|
||||
|
||||
stone(b);
|
||||
for (var i = 0; i < 5; i++) {
|
||||
assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence");
|
||||
var r = assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence")
|
||||
if (r) return r
|
||||
}
|
||||
},
|
||||
|
||||
@@ -167,11 +182,13 @@ return {
|
||||
b.write_bit(true);
|
||||
b.write_pad(8);
|
||||
|
||||
assertEqual(length(b), 8, "Should be padded to 8 bits");
|
||||
var r = assertEqual(length(b), 8, "Should be padded to 8 bits")
|
||||
if (r) return r
|
||||
stone(b);
|
||||
|
||||
assert(b['pad?'](3, 8), "Should detect valid padding at position 3");
|
||||
assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2");
|
||||
r = assert(b['pad?'](3, 8), "Should detect valid padding at position 3")
|
||||
if (r) return r
|
||||
return assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2")
|
||||
},
|
||||
|
||||
test_read_blob_from_stone_blob: function() {
|
||||
@@ -183,10 +200,12 @@ return {
|
||||
|
||||
var b2 = b1.read_blob(4, 12);
|
||||
stone(b2);
|
||||
assertEqual(length(b2), 8, "Read blob should have 8 bits");
|
||||
var r = assertEqual(length(b2), 8, "Read blob should have 8 bits")
|
||||
if (r) return r
|
||||
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
assertEqual(b2.read_logical(5), true);
|
||||
r = assertEqual(b2.read_logical(2), true)
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_logical(5), true)
|
||||
},
|
||||
|
||||
test_stone_blob_is_immutable: function() {
|
||||
@@ -194,35 +213,28 @@ return {
|
||||
b.write_bit(true);
|
||||
stone(b);
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.write_bit(false);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Writing to stone blob should throw error");
|
||||
if (!should_disrupt(function() { b.write_bit(false) }))
|
||||
return "Writing to stone blob should disrupt"
|
||||
},
|
||||
|
||||
test_multiple_stone_calls_are_safe: function() {
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
assert(!stone.p(b), "Blob should not be a stone before stone() call");
|
||||
var r = assert(!stone.p(b), "Blob should not be a stone before stone() call")
|
||||
if (r) return r
|
||||
stone(b);
|
||||
assert(stone.p(b), "Blob should be a stone after stone() call");
|
||||
r = assert(stone.p(b), "Blob should be a stone after stone() call")
|
||||
if (r) return r
|
||||
stone(b);
|
||||
assertEqual(b.read_logical(0), true, "Blob data should remain intact");
|
||||
r = assertEqual(b.read_logical(0), true, "Blob data should remain intact")
|
||||
if (r) return r
|
||||
|
||||
assert(b.stone == null, "blob.stone should not be available as a method");
|
||||
return assert(b.stone == null, "blob.stone should not be available as a method")
|
||||
},
|
||||
|
||||
test_invalid_constructor_arguments: function() {
|
||||
var threw = false;
|
||||
try {
|
||||
var b = Blob("invalid");
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Invalid constructor arguments should throw");
|
||||
if (!should_disrupt(function() { Blob("invalid") }))
|
||||
return "Invalid constructor arguments should disrupt"
|
||||
},
|
||||
|
||||
test_write_bit_validation: function() {
|
||||
@@ -230,13 +242,8 @@ return {
|
||||
b.write_bit(0);
|
||||
b.write_bit(1);
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.write_bit(2);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "write_bit with value 2 should throw");
|
||||
if (!should_disrupt(function() { b.write_bit(2) }))
|
||||
return "write_bit with value 2 should disrupt"
|
||||
},
|
||||
|
||||
test_complex_data_round_trip: function() {
|
||||
@@ -253,15 +260,17 @@ return {
|
||||
|
||||
var b2 = Blob(b);
|
||||
stone(b2);
|
||||
assertEqual(length(b2), originalLength, "Copy should have same length");
|
||||
assertEqual(b2.read_text(0), "Test", "First text should match");
|
||||
var r = assertEqual(length(b2), originalLength, "Copy should have same length")
|
||||
if (r) return r
|
||||
return assertEqual(b2.read_text(0), "Test", "First text should match")
|
||||
},
|
||||
|
||||
test_zero_capacity_blob: function() {
|
||||
var b = Blob(0);
|
||||
assertEqual(length(b), 0, "Zero capacity blob should have length 0");
|
||||
var r = assertEqual(length(b), 0, "Zero capacity blob should have length 0")
|
||||
if (r) return r
|
||||
b.write_bit(true);
|
||||
assertEqual(length(b), 1, "Should expand when writing");
|
||||
return assertEqual(length(b), 1, "Should expand when writing")
|
||||
},
|
||||
|
||||
test_large_blob_handling: function() {
|
||||
@@ -272,13 +281,17 @@ return {
|
||||
b.write_bit(i % 7 == 0);
|
||||
}
|
||||
|
||||
assertEqual(length(b), testSize, "Should have " + testSize + " bits");
|
||||
var r = assertEqual(length(b), testSize, "Should have " + testSize + " bits")
|
||||
if (r) return r
|
||||
stone(b);
|
||||
|
||||
assertEqual(b.read_logical(0), true, "Bit 0 should be true");
|
||||
assertEqual(b.read_logical(7), true, "Bit 7 should be true");
|
||||
assertEqual(b.read_logical(14), true, "Bit 14 should be true");
|
||||
assertEqual(b.read_logical(15), false, "Bit 15 should be false");
|
||||
r = assertEqual(b.read_logical(0), true, "Bit 0 should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(7), true, "Bit 7 should be true")
|
||||
if (r) return r
|
||||
r = assertEqual(b.read_logical(14), true, "Bit 14 should be true")
|
||||
if (r) return r
|
||||
return assertEqual(b.read_logical(15), false, "Bit 15 should be false")
|
||||
},
|
||||
|
||||
test_non_stone_blob_read_methods_should_throw: function() {
|
||||
@@ -287,52 +300,27 @@ return {
|
||||
b.write_number(42);
|
||||
b.write_text("test");
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.read_logical(0);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_logical on non-stone blob should throw");
|
||||
if (!should_disrupt(function() { b.read_logical(0) }))
|
||||
return "read_logical on non-stone blob should disrupt"
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_number(0);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_number on non-stone blob should throw");
|
||||
if (!should_disrupt(function() { b.read_number(0) }))
|
||||
return "read_number on non-stone blob should disrupt"
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_text(0);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_text on non-stone blob should throw");
|
||||
if (!should_disrupt(function() { b.read_text(0) }))
|
||||
return "read_text on non-stone blob should disrupt"
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_blob(0, 10);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "read_blob on non-stone blob should throw");
|
||||
if (!should_disrupt(function() { b.read_blob(0, 10) }))
|
||||
return "read_blob on non-stone blob should disrupt"
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b['pad?'](0, 8);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "pad? on non-stone blob should throw");
|
||||
if (!should_disrupt(function() { b['pad?'](0, 8) }))
|
||||
return "pad? on non-stone blob should disrupt"
|
||||
},
|
||||
|
||||
test_empty_text_write_and_read: function() {
|
||||
var b = Blob();
|
||||
b.write_text("");
|
||||
stone(b);
|
||||
assertEqual(b.read_text(0), "", "Empty string should round-trip");
|
||||
return assertEqual(b.read_text(0), "", "Empty string should round-trip")
|
||||
},
|
||||
|
||||
test_invalid_read_positions: function() {
|
||||
@@ -340,20 +328,10 @@ return {
|
||||
b.write_number(42);
|
||||
stone(b);
|
||||
|
||||
var threw = false;
|
||||
try {
|
||||
b.read_number(-10);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Negative position should throw");
|
||||
if (!should_disrupt(function() { b.read_number(-10) }))
|
||||
return "Negative position should disrupt"
|
||||
|
||||
threw = false;
|
||||
try {
|
||||
b.read_number(1000);
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
assert(threw, "Position beyond length should throw");
|
||||
if (!should_disrupt(function() { b.read_number(1000) }))
|
||||
return "Position beyond length should disrupt"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
return {
|
||||
test_disrupt: function() {
|
||||
throw 1
|
||||
disrupt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ var fd = use("fd")
|
||||
var miniz = use("miniz")
|
||||
var utf8 = use("utf8")
|
||||
|
||||
function safe_unlink(p) { fd.unlink(p) } disruption {}
|
||||
|
||||
return {
|
||||
create_and_read_zip: function() {
|
||||
var ZIP_PATH = "miniz_test.zip"
|
||||
@@ -15,7 +17,8 @@ return {
|
||||
fd.close(handle)
|
||||
}
|
||||
|
||||
try {
|
||||
var error_msg = null
|
||||
var do_test = function() {
|
||||
write_text_file(SOURCE_PATH, PAYLOAD)
|
||||
var source_blob = fd.slurp(SOURCE_PATH)
|
||||
var writer = miniz.write(ZIP_PATH)
|
||||
@@ -26,17 +29,22 @@ return {
|
||||
var reader = miniz.read(zip_blob)
|
||||
|
||||
if (!reader.exists(ENTRY_PATH))
|
||||
throw "entry missing in archive"
|
||||
error_msg = "entry missing in archive"
|
||||
|
||||
if (!error_msg) {
|
||||
var extracted_blob = reader.slurp(ENTRY_PATH)
|
||||
var extracted_text = utf8.decode(extracted_blob)
|
||||
|
||||
if (extracted_text != PAYLOAD)
|
||||
throw "extracted text mismatch"
|
||||
} finally {
|
||||
try { fd.unlink(ZIP_PATH) } catch(e) {}
|
||||
try { fd.unlink(SOURCE_PATH) } catch(e) {}
|
||||
error_msg = "extracted text mismatch"
|
||||
}
|
||||
} disruption {
|
||||
if (!error_msg) error_msg = "test disrupted"
|
||||
}
|
||||
do_test()
|
||||
safe_unlink(ZIP_PATH)
|
||||
safe_unlink(SOURCE_PATH)
|
||||
if (error_msg) return error_msg
|
||||
},
|
||||
|
||||
list_and_count: function() {
|
||||
@@ -44,7 +52,8 @@ return {
|
||||
var ENTRY1 = "file1.txt"
|
||||
var ENTRY2 = "dir/file2.txt"
|
||||
|
||||
try {
|
||||
var error_msg = null
|
||||
var do_test = function() {
|
||||
var writer = miniz.write(ZIP_PATH)
|
||||
writer.add_file(ENTRY1, utf8.encode("content1"))
|
||||
writer.add_file(ENTRY2, utf8.encode("content2"))
|
||||
@@ -55,19 +64,23 @@ return {
|
||||
|
||||
var listed = reader.list()
|
||||
if (length(listed) != reader.count())
|
||||
throw "list/count mismatch"
|
||||
if (length(listed) != 2)
|
||||
throw "unexpected entry count"
|
||||
} finally {
|
||||
try { fd.unlink(ZIP_PATH) } catch(e) {}
|
||||
error_msg = "list/count mismatch"
|
||||
if (!error_msg && length(listed) != 2)
|
||||
error_msg = "unexpected entry count"
|
||||
} disruption {
|
||||
if (!error_msg) error_msg = "test disrupted"
|
||||
}
|
||||
do_test()
|
||||
safe_unlink(ZIP_PATH)
|
||||
if (error_msg) return error_msg
|
||||
},
|
||||
|
||||
exists_check: function() {
|
||||
var ZIP_PATH = "miniz_exists_test.zip"
|
||||
var ENTRY_PATH = "existing.txt"
|
||||
|
||||
try {
|
||||
var error_msg = null
|
||||
var do_test = function() {
|
||||
var writer = miniz.write(ZIP_PATH)
|
||||
writer.add_file(ENTRY_PATH, utf8.encode("data"))
|
||||
writer = null
|
||||
@@ -76,11 +89,14 @@ return {
|
||||
var reader = miniz.read(zip_blob)
|
||||
|
||||
if (!reader.exists(ENTRY_PATH))
|
||||
throw "existing entry not found"
|
||||
if (reader.exists("nonexistent.txt"))
|
||||
throw "nonexistent entry reported as existing"
|
||||
} finally {
|
||||
try { fd.unlink(ZIP_PATH) } catch(e) {}
|
||||
error_msg = "existing entry not found"
|
||||
if (!error_msg && reader.exists("nonexistent.txt"))
|
||||
error_msg = "nonexistent entry reported as existing"
|
||||
} disruption {
|
||||
if (!error_msg) error_msg = "test disrupted"
|
||||
}
|
||||
do_test()
|
||||
safe_unlink(ZIP_PATH)
|
||||
if (error_msg) return error_msg
|
||||
}
|
||||
}
|
||||
|
||||
1820
tests/suite.cm
1820
tests/suite.cm
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
var cmds = {
|
||||
stop: $stop,
|
||||
disrupt: _ => {
|
||||
$delay(_ => { throw Error() }, 0.5)
|
||||
$delay(_ => { disrupt }, 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
tests/use.cm
11
tests/use.cm
@@ -1,5 +1,10 @@
|
||||
try {
|
||||
var load_disrupted = false
|
||||
var do_load = function() {
|
||||
var u = use('tests/use')
|
||||
} catch(e) {
|
||||
log.console(e)
|
||||
} disruption {
|
||||
load_disrupted = true
|
||||
}
|
||||
do_load()
|
||||
if (load_disrupted) {
|
||||
log.console("use self-load disrupted")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user