This commit is contained in:
root
2026-06-15 21:42:50 +00:00
parent 34a97e2577
commit 99cd92fb4a
126 changed files with 1615 additions and 1057 deletions
+13
View File
@@ -260,6 +260,16 @@ void cli_run_starter_parity()
cli_expect_http("uce_starter_parity:starter unknown route 404", "/examples/uce-starter/index.uce?does-not-exist", 404, "<title>404 Not Found | UCE Starter</title>", cli_demo_error_markers());
}
void cli_run_task_lifetime_smoke()
{
String late_file = "/tmp/uce-site-tests-late-task.txt";
file_unlink(late_file);
CliHttpResponse res = cli_frontend("/tests/tasks.uce?mode=late");
usleep(3000000);
String observed = file_exists(late_file) ? file_get_contents(late_file) : "";
cli_test_case("uce_task_lifetime:delayed callback after request return", res.status == 200 && cli_contains(res.body, "late task scheduled") && cli_contains(observed, "late callback ran"), "status=" + std::to_string(res.status) + " observed=" + cli_truncate(observed));
}
void cli_run_tcp_smoke()
{
u64 fd80 = socket_connect("127.0.0.1", 80);
@@ -321,6 +331,9 @@ CLI(Request& context)
cli_run_security_smoke();
print("[GROUP] security ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
group_start = time_precise();
cli_run_task_lifetime_smoke();
print("[GROUP] task-lifetime ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
group_start = time_precise();
cli_run_starter_parity();
print("[GROUP] starter ", std::to_string((u64)((time_precise() - group_start) * 1000)), " ms\n");
group_start = time_precise();
+29 -15
View File
@@ -60,9 +60,11 @@ RENDER(Request& context)
StringList route_parts = {"dashboard", "index", "dashboard", "themes"};
auto unique_routes = list_unique(route_parts);
auto sorted_routes = list_sort(unique_routes);
auto upper_routes = map(sorted_routes, [](String item) { return(to_upper(item)); });
auto dashboard_routes = filter(route_parts, [](String item) { return(item == "dashboard"); });
check("map/filter/list_unique/sort/find/some/every", unique_routes.size() == 3 && sorted_routes[0] == "dashboard" && upper_routes[2] == "THEMES" && dashboard_routes.size() == 2 && list_find(route_parts, [](String item) { return(str_starts_with(item, "them")); }, "missing") == "themes" && list_some(route_parts, [](String item) { return(item == "index"); }) && list_every(unique_routes, [](String item) { return(item != ""); }), join(upper_routes, ","));
auto upper_routes = sorted_routes.map([](String item) { return(to_upper(item)); });
auto dashboard_routes = route_parts.filter([](String item) { return(item == "dashboard"); });
String route_each = "";
route_parts.each([&](String item) { route_each += item + ","; });
check("StringList map/filter/keys/each/list_unique/sort/find/some/every", unique_routes.size() == 3 && sorted_routes[0] == "dashboard" && upper_routes[2] == "THEMES" && dashboard_routes.size() == 2 && join(route_parts.keys(), ",") == "0,1,2,3" && route_each == "dashboard,index,dashboard,themes," && list_find(route_parts, [](String item) { return(str_starts_with(item, "them")); }, "missing") == "themes" && list_some(route_parts, [](String item) { return(item == "index"); }) && list_every(unique_routes, [](String item) { return(item != ""); }), join(upper_routes, ","));
DValue nav;
DValue nav_home;
@@ -79,12 +81,10 @@ RENDER(Request& context)
nav.push(nav_themes);
DValue empty_tree;
DValue empty_pop = empty_tree.pop();
DValue app_nav = dv_filter(nav, [](DValue item, String key) { return(item["section"].to_string() == "app"); });
DValue nav_titles = dv_map(app_nav, [](DValue item, String key) { DValue title; title = item["title"].to_string(); return(title); });
DValue grouped_nav = dv_group_by(nav, [](DValue item, String key) { return(item["section"].to_string()); });
DValue nav_dash_summary = dv_pick(nav_dash, {"title"});
DValue nav_dash_public = dv_omit(nav_dash, {"section"});
check("DValue collection helpers", empty_pop.to_string() == "" && app_nav.is_list() && nav_titles["0"].to_string() == "Dashboard" && grouped_nav["app"]["1"]["title"].to_string() == "Themes" && join(dv_keys(nav_dash_summary), ",") == "title" && dv_values(nav_dash_public)["0"].to_string() == "Dashboard", json_encode(grouped_nav));
DValue app_nav = nav.filter([](DValue item, String key) { return(item["section"].to_string() == "app"); });
DValue nav_titles = app_nav.map([](DValue item, String key) { DValue title; title = item["title"].to_string(); return(title); });
DValue nav_dash_summary = nav_dash.filter({"title"});
check("DValue collection helpers", empty_pop.to_string() == "" && app_nav.is_list() && nav_titles["0"].to_string() == "Dashboard" && join(nav_dash_summary.keys(), ",") == "title" && nav_dash.values()["0"].to_string() == "app" && nav_dash.values()["1"].to_string() == "Dashboard", json_encode(nav_titles));
DValue conv;
conv["name"] = "ada";
@@ -107,7 +107,7 @@ RENDER(Request& context)
}
String ordered_keys = "";
ordered.each([&](const DValue& item, String key) { ordered_keys += key + ","; });
check("DValue list iteration is numeric", ordered_keys == "0,1,2,3,4,5,6,7,8,9,10,11," && dv_values(ordered)["10"].to_string() == "v10" && dv_map(ordered, [](const DValue& item, String key) { return(item); })["11"].to_string() == "v11", ordered_keys);
check("DValue list iteration is numeric", ordered_keys == "0,1,2,3,4,5,6,7,8,9,10,11," && ordered.values()["10"].to_string() == "v10" && ordered.map([](const DValue& item, String key) { return(item); })["11"].to_string() == "v11", ordered_keys);
DValue uceb_source;
uceb_source["name"] = "phase1";
@@ -129,27 +129,31 @@ RENDER(Request& context)
String uceb_error = "";
DValue uceb_decoded;
bool uceb_ok = ucb_decode(uceb_encoded, uceb_decoded, &uceb_error);
check("UCEB1 DValue codec round-trip", uceb_ok && uceb_decoded["name"].to_string() == "phase1" && uceb_decoded["nested"]["answer"].to_string() == "42" && uceb_decoded["float"].to_f64() > 0.00000009 && uceb_decoded["float"].to_f64() < 0.00000011 && uceb_decoded["bool"].to_bool() && uceb_decoded["items"].is_list() && uceb_decoded["items"]["1"].to_string() == "second" && uceb_decoded["empty_list"].is_list() && uceb_decoded["binary"].to_string().size() == uceb_binary.size() && uceb_decoded["binary"].to_string() == uceb_binary && uceb_decoded["pointer"].to_string() == "", "bytes=" + std::to_string((u64)uceb_encoded.size()) + " error=" + uceb_error + " float=" + uceb_decoded["float"].to_string());
check("UCEB2 DValue codec round-trip preserves scalar types", uceb_ok && uceb_decoded["name"].to_string() == "phase1" && uceb_decoded["name"].get_type_name() == "String" && uceb_decoded["nested"]["answer"].to_string() == "42" && uceb_decoded["float"].get_type_name() == "f64" && uceb_decoded["float"].to_f64() > 0.00000009 && uceb_decoded["float"].to_f64() < 0.00000011 && uceb_decoded["bool"].get_type_name() == "bool" && uceb_decoded["bool"].to_bool() && uceb_decoded["items"].is_list() && uceb_decoded["items"]["1"].to_string() == "second" && uceb_decoded["empty_list"].is_list() && uceb_decoded["binary"].to_string().size() == uceb_binary.size() && uceb_decoded["binary"].to_string() == uceb_binary && uceb_decoded["pointer"].to_string() == "", "bytes=" + std::to_string((u64)uceb_encoded.size()) + " error=" + uceb_error + " float=" + uceb_decoded["float"].to_string() + " types=" + uceb_decoded["name"].get_type_name() + "/" + uceb_decoded["float"].get_type_name() + "/" + uceb_decoded["bool"].get_type_name());
DValue uceb_invalid;
String uceb_bad_magic = "NOPE";
uceb_bad_magic.push_back((char)1);
String uceb_wrong_version = "UCEB";
uceb_wrong_version.push_back((char)2);
uceb_wrong_version.push_back((char)3);
String uceb_truncated = "UCEB";
uceb_truncated.push_back((char)1);
uceb_truncated.push_back((char)2);
uceb_truncated.push_back((char)0);
uceb_truncated.push_back('S');
uceb_truncated.push_back((char)0x80);
String uceb_depth_bomb = "UCEB";
uceb_depth_bomb.push_back((char)1);
uceb_depth_bomb.push_back((char)2);
for(u32 i = 0; i < 1030; i++)
{
uceb_depth_bomb.push_back((char)0); // flags
uceb_depth_bomb.push_back('M'); // node type
uceb_depth_bomb.push_back((char)0); // scalar length
uceb_depth_bomb.push_back((char)1); // child count
uceb_depth_bomb.push_back((char)1); // key length
uceb_depth_bomb += "x";
}
uceb_depth_bomb.push_back((char)0);
uceb_depth_bomb.push_back('M');
uceb_depth_bomb.push_back((char)0);
uceb_depth_bomb.push_back((char)0);
String uceb_negative_error = "";
@@ -163,7 +167,7 @@ RENDER(Request& context)
bool uceb_trailing_ok = ucb_decode(uceb_trailing, uceb_invalid, &uceb_negative_error);
String uceb_trailing_error = uceb_negative_error;
bool uceb_depth_ok = ucb_decode(uceb_depth_bomb, uceb_invalid, &uceb_negative_error);
check("UCEB1 rejects invalid input", !uceb_bad_magic_ok && contains(uceb_magic_error, "magic") && !uceb_wrong_version_ok && contains(uceb_version_error, "version") && !uceb_truncated_ok && contains(uceb_truncated_error, "length") && !uceb_trailing_ok && contains(uceb_trailing_error, "trailing") && !uceb_depth_ok && contains(uceb_negative_error, "nesting"), uceb_magic_error + " / " + uceb_version_error + " / " + uceb_truncated_error + " / " + uceb_trailing_error + " / " + uceb_negative_error);
check("UCEB2 rejects invalid input", !uceb_bad_magic_ok && contains(uceb_magic_error, "magic") && !uceb_wrong_version_ok && contains(uceb_version_error, "version") && !uceb_truncated_ok && contains(uceb_truncated_error, "length") && !uceb_trailing_ok && contains(uceb_trailing_error, "trailing") && !uceb_depth_ok && contains(uceb_negative_error, "nesting"), uceb_magic_error + " / " + uceb_version_error + " / " + uceb_truncated_error + " / " + uceb_trailing_error + " / " + uceb_negative_error);
size_t abi_len = 0;
uce_dvalue* abi_root = reinterpret_cast<uce_dvalue*>(&uceb_source);
@@ -205,6 +209,16 @@ RENDER(Request& context)
DValue decoded = json_decode(payload_json);
check("json_encode() / json_decode()", decoded["name"].to_string() == "uce" && int_val(decoded["count"].to_string()) == 3, payload_json);
DValue decoded_unicode = json_decode("\"\\u00A9\"");
DValue decoded_negative = json_decode("-12.5");
DValue decoded_exponent = json_decode("-1.25e+3");
DValue decoded_surrogate = json_decode("\"\\uD83D\\uDE0A\"");
DValue malformed_unicode = json_decode("\"\\u\"");
DValue malformed_number = json_decode("[1e]");
DValue malformed_map = json_decode("{\"a\":");
check("json_decode() unicode / negatives / exponent", decoded_unicode.to_string() == "©" && decoded_surrogate.to_string() == "\xF0\x9F\x98\x8A" && decoded_negative.to_string() == "-12.5" && decoded_exponent.to_string() == "-1.25e+3", "unicode=" + decoded_unicode.to_string() + " surrogate=" + decoded_surrogate.to_string() + " negative=" + decoded_negative.to_string() + " exponent=" + decoded_exponent.to_string());
check("json_decode() malformed input is safe", malformed_unicode.to_string() == "" && malformed_number["0"].to_string() == "1" && malformed_map.has("a"), malformed_unicode.to_string() + " / " + malformed_number.to_string() + " / " + malformed_map["a"].to_string());
DValue xml_doc;
xml_doc["name"] = "book";
xml_doc["attrs"]["id"] = "b1";
+5 -3
View File
@@ -62,11 +62,13 @@ RENDER(Request& context)
check("cwd_get() / cwd_set() / process_start_directory()", old_cwd != "" && tmp_cwd == "/tmp" && process_start_directory() == start_dir && start_dir != "", "old=" + old_cwd + " tmp=" + tmp_cwd + " start=" + start_dir);
StringMap cfg;
cfg["u64"] = "42";
cfg["f64"] = "3.5";
cfg["u64"] = " 42 ";
cfg["s64"] = " -12 ";
cfg["bad"] = "42x";
cfg["f64"] = " 3.5 ";
cfg["yes"] = "yes";
cfg["no"] = "off";
check("config_map_*() / config_*() helpers", config_map_u64(cfg, "u64", 7) == 42 && config_map_u64(cfg, "bad", 7) == 7 && config_map_f64(cfg, "f64", 1.0) > 3.49 && !config_map_bool(cfg, "no") && config_bool_value("false") == false && config_u64("NO_SUCH_UCE_TEST_KEY", 99) == 99 && config_f64("NO_SUCH_UCE_TEST_KEY", 1.25) == 1.25 && !config_bool("NO_SUCH_UCE_TEST_KEY", false), var_dump(cfg));
check("to_u64() / to_s64() / to_f64() / to_bool()", to_u64(cfg["u64"], 7) == 42 && to_u64(cfg["bad"], 7) == 7 && to_s64(cfg["s64"], 5) == -12 && to_s64(cfg["bad"], 5) == 5 && to_f64(cfg["f64"], 1.0) > 3.49 && to_f64(cfg["bad"], 1.25) == 1.25 && to_bool(cfg["yes"], false) && !to_bool(cfg["no"], true) && !to_bool("unknown", false), var_dump(cfg));
DValue perf = request_perf();
check("request_perf()", perf.get_type_name() != "invalid", json_encode(perf));
+15
View File
@@ -22,6 +22,21 @@ RENDER(Request& context)
};
String mode = first(context.get["mode"], "run");
if(mode == "late")
{
String late_file = "/tmp/uce-site-tests-late-task.txt";
file_unlink(late_file);
pid_t late_pid = task("site-tests-late-callback", [late_file]() {
sleep(2);
DValue perf = request_perf();
file_put_contents(late_file, "late callback ran pid=" + std::to_string((u64)perf["worker_pid"].to_s64()) + " type=" + perf["worker_pid"].get_type_name());
}, 10);
site_tests_page_start("Tasks", "Schedules a delayed task callback and returns before the callback runs.");
check("late task scheduled", late_pid != 0, "started=" + std::to_string(late_pid));
site_tests_summary(passed, failed, skipped, "The CLI runner verifies this callback after the spawning request has returned.");
site_tests_page_end();
return;
}
pid_t repeat_existing = task_pid("site-tests-repeat");
if(mode == "stop" && repeat_existing != 0)
task_kill(repeat_existing, 15);
+31 -1
View File
@@ -36,6 +36,8 @@ RENDER(Request& context)
const socketDetail = document.getElementById('socket-detail');
let sawHello = false;
let sawBroadcast = false;
let sawState = false;
let stateAck = null;
function mark(el, detailEl, ok, text) {
el.textContent = ok ? 'PASS' : 'FAIL';
@@ -67,10 +69,19 @@ RENDER(Request& context)
if (payload.type === 'hello-ack') {
sawHello = true;
mark(helloStatus, helloDetail, true, JSON.stringify(payload, null, 2));
ws.send(JSON.stringify({ type: 'set-state', nonce }));
ws.send(JSON.stringify({ type: 'read-state', nonce }));
ws.send(JSON.stringify({ type: 'broadcast', nonce }));
return;
}
if (payload.type === 'state-ack') {
sawState = true;
stateAck = payload;
mark(socketStatus, socketDetail, true, `state count=${payload.state_count}`);
return;
}
if (payload.type === 'broadcast-ack') {
sawBroadcast = true;
mark(broadcastStatus, broadcastDetail, true, JSON.stringify(payload, null, 2));
@@ -87,12 +98,17 @@ RENDER(Request& context)
ws.addEventListener('close', () => {
if (!sawHello) mark(helloStatus, helloDetail, false, 'socket closed before hello acknowledgment');
if (!sawBroadcast) mark(broadcastStatus, broadcastDetail, false, 'socket closed before broadcast acknowledgment');
if (!sawHello || !sawBroadcast) mark(socketStatus, socketDetail, false, 'socket closed before suite completed');
if (!sawState) {
mark(socketStatus, socketDetail, false, 'socket closed before state round trip');
} else {
mark(socketStatus, socketDetail, true, `state count=${stateAck && stateAck.state_count}`);
}
});
window.setTimeout(() => {
if (!sawHello) mark(helloStatus, helloDetail, false, 'timeout waiting for hello acknowledgment');
if (!sawBroadcast) mark(broadcastStatus, broadcastDetail, false, 'timeout waiting for broadcast acknowledgment');
if (!sawState) mark(socketStatus, socketDetail, false, 'timeout waiting for state-ack');
}, 5000);
</script><?
site_tests_page_end();
@@ -122,5 +138,19 @@ WS(Request& context)
return;
}
if(type == "set-state")
{
context.connection["state_count"].set((f64)((u64)context.connection["state_count"].to_u64() + 1));
return;
}
if(type == "read-state")
{
DValue event = websocket_suite_event(context, "state-ack", nonce);
event["state_count"] = (f64)context.connection["state_count"].to_u64();
ws_send_to(ws_connection_id(), json_encode(event));
return;
}
ws_send_to(ws_connection_id(), json_encode(websocket_suite_event(context, "unknown", nonce)));
}