#include "zeroerr/assert.h" #include "zeroerr/unittest.h" #include "common/Module.h" #include "platform_event/PlatformEvent.h" #include "scripts.h" #include "thread/Thread.h" #include "timer/Timer.h" #include #include #include namespace { void exposeCore(ssq::VM& vm) { eve::ModuleManager::expose(vm); } std::string runAsyncSnippet(const std::string& body) { ssq::VM vm(1024, ssq::Libs::ALL); exposeCore(vm); { ssq::Table eve(vm.find("eve")); eve.set("asyncScript", std::string(eve::async_content ? eve::async_content : "")); } // Minimal globals expected by async.nut / load prelude. std::string prelude = R"( timer <- eve.Timer(); thread <- eve.Thread(); event <- eve.PlatformEvent(); if ("asyncScript" in eve && eve.asyncScript != "") compilestring(eve.asyncScript)(); )"; vm.run(vm.compileSource((prelude + "\n" + body).c_str())); // Return a global `result` string if set. try { return vm.find("result").toString(); } catch (...) { return {}; } } } // namespace TEST_CASE("async.promise.resolveThen") { std::string out = runAsyncSnippet(R"( result <- "fail"; local p = Promise.resolve(42); p.then(function(v) { result = "" + v; }); async_pump(); async_pump(); )"); CHECK_EQ(out, std::string("42")); } TEST_CASE("async.setTimeout") { std::string out = runAsyncSnippet(R"( result <- "pending"; setTimeout(function() { result = "fired"; }, 5); local t0 = timer.getTime(); while (timer.getTime() - t0 < 0.05) { // busy wait wall time; timer uses SDL performance counter } // Advance timer.step so getTime keeps moving; fire timers. for (local i = 0; i < 5; i++) { timer.step(); async_pump(); } // If still pending, force due by pumping after sleep via thread if (result == "pending") { local pool = thread.getPool(); local task = pool.submitSleep(20); task.wait(); timer.step(); async_pump(); async_pump(); } )"); CHECK_EQ(out, std::string("fired")); } TEST_CASE("async.asyncSleep") { std::string out = runAsyncSnippet(R"( result <- "pending"; asyncSleep(1).then(function(v) { result = "ok"; }); local pool = thread.getPool(); local task = pool.submitSleep(15); task.wait(); for (local i = 0; i < 8; i++) { timer.step(); async_pump(); } )"); CHECK_EQ(out, std::string("ok")); } TEST_CASE("async.asyncDelay.worker") { std::string out = runAsyncSnippet(R"( result <- "pending"; asyncDelay(5, "hello").then(function(v) { result = v; }); local pool = thread.getPool(); local task = pool.submitSleep(30); task.wait(); for (local i = 0; i < 10; i++) { async_pump(); } )"); CHECK_EQ(out, std::string("hello")); } TEST_CASE("async.asyncPost.sameNameCorrelatedByTask") { std::string out = runAsyncSnippet(R"( result <- "pending"; local slow = ""; local fast = ""; asyncPost("same-name-correlation", "slow", 25).then(function(v) { slow = v; }); asyncPost("same-name-correlation", "fast", 1).then(function(v) { fast = v; }); thread.getPool().waitAll(); local name = event.poll(); while (name != "") { async_dispatch_event(name, event.getLastData()); name = event.poll(); } for (local i = 0; i < 6; i++) async_pump(); result = slow + "," + fast; )"); CHECK_EQ(out, std::string("slow,fast")); } TEST_CASE("async.postMain.event") { auto* th = eve::thread::Thread::create(); auto* ev = eve::platform_event::PlatformEvent::create(); th->postMain("asynctest", "payload"); CHECK_EQ(ev->pollName(), std::string("asynctest")); CHECK_EQ(ev->getLastData(), std::string("payload")); } TEST_CASE("async.Promise.all") { std::string out = runAsyncSnippet(R"( result <- "fail"; Promise.all([Promise.resolve(1), Promise.resolve(2)]).then(function(arr) { result = "" + arr[0] + "," + arr[1]; }); async_pump(); async_pump(); async_pump(); )"); CHECK_EQ(out, std::string("1,2")); } TEST_CASE("async.asyncSeq") { std::string out = runAsyncSnippet(R"( result <- "fail"; asyncSeq([ function() { return Promise.resolve(1); }, function(v) { return Promise.resolve(v + 2); }, function(v) { return v * 10; }, ]).then(function(v) { result = "" + v; }); async_pump(); async_pump(); async_pump(); async_pump(); async_pump(); )"); CHECK_EQ(out, std::string("30")); } TEST_CASE("async.eveScriptAwaitStateMachine") { std::string out = runAsyncSnippet(R"( result <- "pending"; class Counter { offset = 4; async function compute(limit: int) -> int { local total = this.offset; for (local i = 0; i < limit; ++i) total += await Promise.resolve(i); try { await Promise.reject("expected"); } catch (e) { total += 10; } return total; } } Counter().compute(4).then( function(v) { result = "" + v; }, function(e) { result = "error:" + e; } ); for (local i = 0; i < 32; ++i) async_pump(); )"); CHECK_EQ(out, std::string("20")); } TEST_CASE("async.hotReloadCancelsOldContinuations") { std::string out = runAsyncSnippet(R"( result <- "pending"; async function wait_forever() { await Promise(function(resolve, reject) {}); return "unexpected"; } wait_forever().then( function(v) { result = v; }, function(e) { result = e; } ); async_cancel_continuations("test reload"); async_pump(); async_pump(); )"); CHECK(out.find("async continuation cancelled: test reload") != std::string::npos); }