feat: more clear definition and first base implementation for lua
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
103
test/bds/scripting/job_test.exs
Normal file
103
test/bds/scripting/job_test.exs
Normal file
@@ -0,0 +1,103 @@
|
||||
defmodule BDS.Scripting.JobTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
defmodule FakeRuntime do
|
||||
@behaviour BDS.Scripting.Runtime
|
||||
|
||||
@impl true
|
||||
def validate(_source), do: :ok
|
||||
|
||||
@impl true
|
||||
def execute(_source, _entrypoint, _args, opts) do
|
||||
if callback = Keyword.get(opts, :on_progress) do
|
||||
callback.(%{"phase" => "started", "current" => 1, "total" => 2})
|
||||
end
|
||||
|
||||
Process.sleep(50)
|
||||
{:ok, "done"}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BlockingRuntime do
|
||||
@behaviour BDS.Scripting.Runtime
|
||||
|
||||
@impl true
|
||||
def validate(_source), do: :ok
|
||||
|
||||
@impl true
|
||||
def execute(_source, _entrypoint, _args, opts) do
|
||||
if callback = Keyword.get(opts, :on_progress) do
|
||||
callback.(%{"phase" => "started", "current" => 1, "total" => 2})
|
||||
end
|
||||
|
||||
receive do
|
||||
:never -> :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setup do
|
||||
original = Application.fetch_env!(:bds, :scripting)
|
||||
|
||||
on_exit(fn ->
|
||||
Application.put_env(:bds, :scripting, original)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "runs long-lived script jobs asynchronously and tracks progress" do
|
||||
Application.put_env(:bds, :scripting,
|
||||
runtime: FakeRuntime,
|
||||
timeout: 300_000,
|
||||
max_reductions: 5_000_000,
|
||||
job_timeout: :infinity,
|
||||
job_max_reductions: :none
|
||||
)
|
||||
|
||||
assert {:ok, job} = BDS.Scripting.start_job("irrelevant", "main")
|
||||
assert job.status in [:queued, :running]
|
||||
|
||||
running_job = wait_for_job(job.id, &(&1.status == :running and &1.progress == %{"phase" => "started", "current" => 1, "total" => 2}))
|
||||
assert running_job.started_at != nil
|
||||
|
||||
completed_job = wait_for_job(job.id, &(&1.status == :completed))
|
||||
assert completed_job.result == "done"
|
||||
assert completed_job.finished_at != nil
|
||||
end
|
||||
|
||||
test "cancels managed script jobs" do
|
||||
Application.put_env(:bds, :scripting,
|
||||
runtime: BlockingRuntime,
|
||||
timeout: 300_000,
|
||||
max_reductions: 5_000_000,
|
||||
job_timeout: :infinity,
|
||||
job_max_reductions: :none
|
||||
)
|
||||
|
||||
assert {:ok, job} = BDS.Scripting.start_job("irrelevant", "main")
|
||||
_running_job = wait_for_job(job.id, &(&1.status == :running))
|
||||
|
||||
assert :ok = BDS.Scripting.cancel_job(job.id)
|
||||
|
||||
cancelled_job = wait_for_job(job.id, &(&1.status == :cancelled))
|
||||
assert cancelled_job.finished_at != nil
|
||||
end
|
||||
|
||||
defp wait_for_job(job_id, predicate, attempts \\ 50)
|
||||
|
||||
defp wait_for_job(job_id, predicate, attempts) when attempts > 0 do
|
||||
job = BDS.Scripting.get_job(job_id)
|
||||
|
||||
if predicate.(job) do
|
||||
job
|
||||
else
|
||||
Process.sleep(20)
|
||||
wait_for_job(job_id, predicate, attempts - 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_job(_job_id, _predicate, 0) do
|
||||
flunk("job did not reach expected state")
|
||||
end
|
||||
end
|
||||
43
test/bds/scripting/lua_test.exs
Normal file
43
test/bds/scripting/lua_test.exs
Normal file
@@ -0,0 +1,43 @@
|
||||
defmodule BDS.Scripting.LuaTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "validates Lua source" do
|
||||
assert :ok = BDS.Scripting.validate("function main() return 42 end")
|
||||
end
|
||||
|
||||
test "rejects invalid Lua source" do
|
||||
assert {:error, {:compile_error, _details}} = BDS.Scripting.validate("function main(")
|
||||
end
|
||||
|
||||
test "executes the configured entrypoint in a sandboxed Lua runtime" do
|
||||
source = "function main(a, b) return a + b end"
|
||||
|
||||
assert {:ok, 42} = BDS.Scripting.execute(source, "main", [19, 23])
|
||||
end
|
||||
|
||||
test "exposes progress reporting through the host boundary" do
|
||||
parent = self()
|
||||
|
||||
source = """
|
||||
function main()
|
||||
bds.report_progress({phase = 'fetch', current = 1, total = 2})
|
||||
bds.report_progress({phase = 'write', current = 2, total = 2})
|
||||
return 'done'
|
||||
end
|
||||
"""
|
||||
|
||||
callback = fn progress -> send(parent, {:progress, progress}) end
|
||||
|
||||
assert {:ok, "done"} = BDS.Scripting.execute(source, "main", [], on_progress: callback)
|
||||
|
||||
assert_receive {:progress, %{"phase" => "fetch", "current" => 1, "total" => 2}}
|
||||
assert_receive {:progress, %{"phase" => "write", "current" => 2, "total" => 2}}
|
||||
end
|
||||
|
||||
test "enforces reduction limits" do
|
||||
source = "function main() while true do end end"
|
||||
|
||||
assert {:error, {:reductions_exceeded, _count}} =
|
||||
BDS.Scripting.execute(source, "main", [], timeout: 1_000, max_reductions: 100)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user