Add infrastructure for JS testing
This commit is contained in:
parent
dfb24274b0
commit
d3ea64cc15
15 changed files with 368 additions and 131 deletions
3
config/test.exs
Normal file
3
config/test.exs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Config
|
||||||
|
|
||||||
|
config :nodejs, debug_mode: true
|
||||||
14
js/index.mjs
Normal file
14
js/index.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
class Boxed {
|
||||||
|
constructor(type, value) {
|
||||||
|
this.type = type;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const boxed = (type, value) => {
|
||||||
|
return new Boxed(type, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
boxed,
|
||||||
|
};
|
||||||
128
lib/beam_js.ex
128
lib/beam_js.ex
|
|
@ -2,132 +2,10 @@ defmodule BeamJs do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Documentation for `BeamJs`.
|
Documentation for `BeamJs`.
|
||||||
"""
|
"""
|
||||||
require Logger
|
|
||||||
|
|
||||||
def module_code(module) do
|
|
||||||
module
|
|
||||||
|> BeamFile.byte_code()
|
|
||||||
|> ok(fn {_, _, _, _, _, sections} ->
|
|
||||||
{:ok, Enum.filter(sections, fn
|
|
||||||
{:function, _fun_name, _arity, _, _code} ->
|
|
||||||
true
|
|
||||||
_other ->
|
|
||||||
false
|
|
||||||
end)}
|
|
||||||
end)
|
|
||||||
|> ok(fn functions ->
|
|
||||||
Enum.map(functions, fn {:function, name, arity, _, code} ->
|
|
||||||
{"#{name}/#{arity}", transform_function_code(code)}
|
|
||||||
end)
|
|
||||||
|> Map.new()
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def transform_function_code(ops) do
|
defimpl JSON.Encoder, for: BeamJs.Boxed do
|
||||||
Enum.reduce(ops, [], fn
|
def encode(value, encoder) do
|
||||||
{:label, number}, [{line_no, head_ops} | tail] ->
|
["BeamJs.boxed(", encoder.(value.type, encoder), ", ", encoder.(value.value, encoder),")"]
|
||||||
[{number, []} | [{line_no, Enum.reverse(head_ops)} | tail]]
|
|
||||||
|
|
||||||
{:label, number}, acc ->
|
|
||||||
[{number, []} | acc]
|
|
||||||
|
|
||||||
{:line, _line_number}, [] ->
|
|
||||||
[]
|
|
||||||
|
|
||||||
other_op, [{number, head} | tail] ->
|
|
||||||
[{number, [other_op|head]} | tail]
|
|
||||||
end)
|
|
||||||
|> Enum.reverse()
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_fun(label_sections) do
|
|
||||||
"{\n" <> Enum.map_join(label_sections, ",\n", fn {label, ops} ->
|
|
||||||
body = Enum.map_join(ops, "", &encode_op/1)
|
|
||||||
" #{label}: (p) => {\n#{body}}"
|
|
||||||
end) <> "}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_op(tuple) when is_tuple(tuple) do
|
|
||||||
op_name = elem(tuple, 0)
|
|
||||||
args =
|
|
||||||
try do
|
|
||||||
encode_args(tuple)
|
|
||||||
rescue
|
|
||||||
e ->
|
|
||||||
Logger.error("Error encoding op: #{inspect(tuple)}")
|
|
||||||
reraise e, __STACKTRACE__
|
|
||||||
end
|
|
||||||
|
|
||||||
" BeamJs.Ops.#{op_name}(p, " <> Enum.join(args, ", ") <> ");\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_op(atom) when is_atom(atom), do: " BeamJs.Ops.#{atom}(p);\n"
|
|
||||||
|
|
||||||
def encode_args({:allocate, frame_size, live}), do: [number(frame_size), number(live)]
|
|
||||||
def encode_args({:line, number}), do: [number(number)]
|
|
||||||
def encode_args({:func_info, {:atom, m}, {:atom, f}, arity}), do: [quoted_string(m), quoted_string(f), number(arity)]
|
|
||||||
def encode_args({:select_val, source, {:f, fail_label}, comparison}), do: [encode_source(source), number(fail_label), encode_value(comparison)]
|
|
||||||
def encode_args({:move, source, {register_bank, number}}) when register_bank in [:x, :y], do: [encode_source(source), quoted_string(register_bank), number(number)]
|
|
||||||
def encode_args({:call, _live, {_m, f, a}}), do: [quoted_string(f), number(a)]
|
|
||||||
def encode_args({:call_ext_only, _live, {:extfunc, m, f, a}}), do: [quoted_string(m), quoted_string(f), number(a)]
|
|
||||||
def encode_args({:call_only, _live, {_m, f, a}}), do: [quoted_string(f), number(a)]
|
|
||||||
def encode_args({:deallocate, live}), do: [number(live)]
|
|
||||||
def encode_args({:gc_bif, bif, {:f, fail_to}, _unknown, args, {register_bank, number}}) when register_bank in [:x, :y] do
|
|
||||||
[
|
|
||||||
quoted_string(bif),
|
|
||||||
number(fail_to),
|
|
||||||
"[" <> (args |> Enum.map(&encode_source/1) |> Enum.join(", ")) <> "]",
|
|
||||||
register_bank,
|
|
||||||
number
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_source({:tr, source, _}), do: encode_source(source)
|
|
||||||
def encode_source({:x, number}), do: "p.registers.x[#{number}]"
|
|
||||||
def encode_source({:y, number}), do: "p.registers.y[#{number}]"
|
|
||||||
def encode_source({:integer, number}), do: number(number)
|
|
||||||
def encode_source(value), do: encode_value(value)
|
|
||||||
|
|
||||||
|
|
||||||
def encode_value({:literal, value}), do: encode_value(value)
|
|
||||||
|
|
||||||
def encode_value({type, value}) when type in [:atom, :x, :y, :list] do
|
|
||||||
~s|BeamJs.boxed("#{type}", #{encode_value(value)})|
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_value(tuple) when is_tuple(tuple) do
|
|
||||||
~s|BeamJs.boxed("tuple", #{encode_value(Tuple.to_list(tuple))})|
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_value(value) when is_binary(value) do
|
|
||||||
if String.valid?(value) do
|
|
||||||
quoted_string(value)
|
|
||||||
else
|
|
||||||
# Likely a raw bytes array
|
|
||||||
base64 = value |> Base.encode64() |> quoted_string()
|
|
||||||
~s|atob(#{base64})|
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_value(value) when is_list(value) do
|
|
||||||
~s|[#{Enum.map_join(value, ", ", &encode_value/1)}]|
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_value(value) when is_atom(value) do
|
|
||||||
quoted_string(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_value(value) when is_number(value) do
|
|
||||||
"#{value}"
|
|
||||||
end
|
|
||||||
|
|
||||||
defp quoted_string(v), do: ~s|"#{v}"|
|
|
||||||
defp number(n), do: "#{n}"
|
|
||||||
|
|
||||||
def fact(0), do: 1
|
|
||||||
def fact(1), do: 1
|
|
||||||
def fact(n), do: fact(n - 1) + fact(n - 2)
|
|
||||||
|
|
||||||
defp ok({:ok, value}, fun), do: fun.(value)
|
|
||||||
defp ok(other, _fun), do: other
|
|
||||||
end
|
|
||||||
|
|
|
||||||
3
lib/beam_js/boxed.ex
Normal file
3
lib/beam_js/boxed.ex
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule BeamJs.Boxed do
|
||||||
|
defstruct [:type, :value]
|
||||||
|
end
|
||||||
191
lib/beam_js/encoder.ex
Normal file
191
lib/beam_js/encoder.ex
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
defmodule BeamJs.Encoder do
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias BeamJs.Boxed
|
||||||
|
|
||||||
|
def encode_module(mod_ast) do
|
||||||
|
"import BeamJs from '../js/index.mjs';\n" <>
|
||||||
|
(
|
||||||
|
mod_ast
|
||||||
|
|> Enum.map_join("\n", fn {fn_name, ops} ->
|
||||||
|
encode_fun_to_js_export(ops, fn_name)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_fun_to_js_value(label_sections) do
|
||||||
|
Enum.map(label_sections, fn {label, ops} ->
|
||||||
|
body = Enum.map(ops, &encode_op/1)
|
||||||
|
[label, body]
|
||||||
|
end)
|
||||||
|
|> JSON.encode!()
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_fun_to_js_export(label_sections, function_name) do
|
||||||
|
label_sections
|
||||||
|
|> encode_fun_to_js_value()
|
||||||
|
|> then(&"export const #{function_name} = #{&1};")
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_op(tuple) when is_tuple(tuple) do
|
||||||
|
op_name = elem(tuple, 0)
|
||||||
|
args =
|
||||||
|
try do
|
||||||
|
encode_op_args(tuple)
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
Logger.error("Error encoding op: #{inspect(tuple)}")
|
||||||
|
reraise e, __STACKTRACE__
|
||||||
|
end
|
||||||
|
|
||||||
|
[op_name | args]
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_op(atom) when is_atom(atom), do: [atom]
|
||||||
|
|
||||||
|
def encode_op_args(op)
|
||||||
|
def encode_op_args({:allocate, frame_size, live}), do: [frame_size, live]
|
||||||
|
def encode_op_args({:allocate_heap, n, {:alloc, list}, n2}) do
|
||||||
|
[
|
||||||
|
n,
|
||||||
|
Enum.map(list, &encode_value/1),
|
||||||
|
n2
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:bs_create_bin, {:f, fail_to}, alloc, live, unit, {dest_bank, dest_number}, {:list, segments}}) do
|
||||||
|
[
|
||||||
|
fail_to,
|
||||||
|
alloc,
|
||||||
|
live,
|
||||||
|
unit,
|
||||||
|
dest_bank,
|
||||||
|
dest_number,
|
||||||
|
Enum.map(segments, &encode_value/1)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:line, number}), do: [number]
|
||||||
|
def encode_op_args({:func_info, {:atom, m}, {:atom, f}, arity}), do: [Atom.to_string(m), Atom.to_string(f), arity]
|
||||||
|
def encode_op_args({:make_fun3, {m, f, a}, x, y, {dest_bank, dest_number}, {:list, list}}) do
|
||||||
|
[
|
||||||
|
m,
|
||||||
|
f,
|
||||||
|
a,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
dest_bank,
|
||||||
|
dest_number,
|
||||||
|
Enum.map(list, &encode_value/1)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:select_val, source, {:f, fail_label}, {:list, f_list}}), do: [encode_source(source), fail_label, encode_f_list(f_list)]
|
||||||
|
def encode_op_args({:move, source, dest}), do: [encode_source(source), encode_source(dest)]
|
||||||
|
def encode_op_args({:call, _live, {_m, f, a}}), do: [f, a]
|
||||||
|
def encode_op_args({:call_ext, _live, {:extfunc, m, f, a}}), do: [m, f, a]
|
||||||
|
def encode_op_args({:call_ext_only, _live, {:extfunc, m, f, a}}), do: [m, f, a]
|
||||||
|
def encode_op_args({:call_only, _live, {_m, f, a}}), do: [f, a]
|
||||||
|
def encode_op_args({:deallocate, live}), do: [live]
|
||||||
|
def encode_op_args({:gc_bif, bif, {:f, fail_to}, _unknown, args, {register_bank, number}}) when register_bank in [:x, :y] do
|
||||||
|
[
|
||||||
|
bif,
|
||||||
|
fail_to,
|
||||||
|
Enum.map(args, &encode_source/1),
|
||||||
|
register_bank,
|
||||||
|
number
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:get_tuple_element, {source_bank, source_number}, index, {dest_bank, dest_number}}) when source_bank in [:x, :y] and dest_bank in [:x, :y] do
|
||||||
|
[
|
||||||
|
source_bank,
|
||||||
|
source_number,
|
||||||
|
index,
|
||||||
|
dest_bank,
|
||||||
|
dest_number
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:init_yregs, {:list, sources}}), do: [Enum.map(sources, &encode_source/1)]
|
||||||
|
def encode_op_args({:jump, {:f, to}}), do: [to]
|
||||||
|
def encode_op_args({:put_list, {bank_a, number_a}, {bank_b, number_b}, {bank_c, number_c}}), do: [bank_a, number_a, bank_b, number_b, bank_c, number_c]
|
||||||
|
def encode_op_args({:put_list, {bank_a, number_a}, nil, {bank_c, number_c}}), do: [bank_a, number_a, nil, nil, bank_c, number_c]
|
||||||
|
def encode_op_args({:bif, bif, {:f, fail_to}, args, {register_bank, number}}) when register_bank in [:x, :y] do
|
||||||
|
[
|
||||||
|
bif,
|
||||||
|
fail_to,
|
||||||
|
Enum.map(args, &encode_source/1),
|
||||||
|
register_bank,
|
||||||
|
number
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:select_tuple_arity, {:tr, {source_bank, source_number}, {:t_tuple, 0, false, %{}}}, {:f, fail_to},
|
||||||
|
{:list, f_list}}) do
|
||||||
|
[
|
||||||
|
source_bank,
|
||||||
|
source_number,
|
||||||
|
fail_to,
|
||||||
|
encode_f_list(f_list)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:swap, {a_bank, a_n}, {b_bank, b_n}}), do: [a_bank, a_n, b_bank, b_n]
|
||||||
|
def encode_op_args({:test, test_name, {:f, fail_to}, args}) do
|
||||||
|
[
|
||||||
|
test_name,
|
||||||
|
fail_to,
|
||||||
|
Enum.map(args, &encode_source/1)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
def encode_op_args({:test_heap, {:alloc, list}, b}), do: [Enum.map(list, &encode_value/1), b]
|
||||||
|
def encode_op_args({:test_heap, a, b}), do: [a, b]
|
||||||
|
def encode_op_args({:trim, n, remaining}), do: [n, remaining]
|
||||||
|
def encode_op_args({:try, {register_bank, number}, {:f, fail_to}}) when register_bank in [:x, :y], do: [register_bank, number, fail_to]
|
||||||
|
def encode_op_args({:try_case, {register_bank, number}}) when register_bank in [:x, :y], do: [register_bank, number]
|
||||||
|
def encode_op_args({:try_end, {register_bank, number}}) when register_bank in [:x, :y], do: [register_bank, number]
|
||||||
|
|
||||||
|
def encode_f_list(comparison) do
|
||||||
|
comparison
|
||||||
|
|> Enum.chunk_every(2)
|
||||||
|
|> Enum.map(fn [value, {:f, number}] ->
|
||||||
|
[encode_value(value), number]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_source({:tr, source, _}), do: encode_source(source)
|
||||||
|
def encode_source({:x, number}), do: [:x, number]
|
||||||
|
def encode_source({:y, number}), do: [:y, number]
|
||||||
|
def encode_source(value), do: encode_value(value)
|
||||||
|
|
||||||
|
|
||||||
|
def encode_value({:literal, value}), do: encode_value(value)
|
||||||
|
|
||||||
|
def encode_value({type, value}) when type in [:atom, :list, :integer] do
|
||||||
|
%Boxed{type: type, value: value}
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_value(tuple) when is_tuple(tuple) do
|
||||||
|
%Boxed{type: "tuple", value: encode_value(Tuple.to_list(tuple))}
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_value(value) when is_binary(value) do
|
||||||
|
if String.valid?(value) do
|
||||||
|
value
|
||||||
|
else
|
||||||
|
# Likely a raw bytes array
|
||||||
|
base64 = value |> Base.encode64() |> quoted_string()
|
||||||
|
~s|atob(#{base64})|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_value(value) when is_list(value) do
|
||||||
|
Enum.map(value, &encode_value/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_value(value) when is_atom(value) do
|
||||||
|
%Boxed{type: :atom, value: value}
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_value(value) when is_integer(value) do
|
||||||
|
%Boxed{type: :integer, value: value}
|
||||||
|
end
|
||||||
|
|
||||||
|
def encode_value(map) when is_map(map), do: %Boxed{type: :map, value: map |> Enum.map(fn {key, value} -> {encode_value(key), encode_value(value)} end) |> Map.new()}
|
||||||
|
|
||||||
|
defp quoted_string(v), do: ~s|"#{v}"|
|
||||||
|
end
|
||||||
21
lib/beam_js/function.ex
Normal file
21
lib/beam_js/function.ex
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
defmodule BeamJs.Function do
|
||||||
|
def to_ast(ops) do
|
||||||
|
Enum.reduce(ops, [], fn
|
||||||
|
{:label, number}, [{line_no, head_ops} | tail] ->
|
||||||
|
[{number, []} | [{line_no, Enum.reverse(head_ops)} | tail]]
|
||||||
|
|
||||||
|
{:label, number}, acc ->
|
||||||
|
[{number, []} | acc]
|
||||||
|
|
||||||
|
{:line, _line_number}, [] ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
other_op, [{number, head} | tail] ->
|
||||||
|
[{number, [other_op|head]} | tail]
|
||||||
|
end)
|
||||||
|
|> then(fn [{label, head_ops}|tail] ->
|
||||||
|
[{label, Enum.reverse(head_ops)}|tail]
|
||||||
|
end)
|
||||||
|
|> Enum.reverse()
|
||||||
|
end
|
||||||
|
end
|
||||||
53
lib/beam_js/js_runtime.ex
Normal file
53
lib/beam_js/js_runtime.ex
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
defmodule BeamJs.JsRuntime do
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
def start_link() do
|
||||||
|
GenServer.start_link(__MODULE__, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(pid, code) do
|
||||||
|
GenServer.call(pid, {:run, code})
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def init(_opts) do
|
||||||
|
path = System.find_executable("node")
|
||||||
|
port = Port.open({:spawn_executable, path}, [:binary, args: ["-i"]])
|
||||||
|
wait_for_prompt()
|
||||||
|
|
||||||
|
{:ok, %{port: port}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def handle_call({:run, code}, _from, %{port: port} = state) do
|
||||||
|
send(port, {self(), {:command, code <> "\r\n"}})
|
||||||
|
result =
|
||||||
|
receive do
|
||||||
|
{_port, {:data, msg}} -> msg
|
||||||
|
end
|
||||||
|
|
||||||
|
result =
|
||||||
|
result
|
||||||
|
|> String.trim_trailing("\n> ")
|
||||||
|
|> String.trim("'")
|
||||||
|
case JSON.decode(result) do
|
||||||
|
{:ok, result} ->
|
||||||
|
{:reply, {:ok, result}, state}
|
||||||
|
{:error, _decode_error} ->
|
||||||
|
{:reply, {:error, result}, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def terminate(_reason, %{port: port}) do
|
||||||
|
Port.close(port)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp wait_for_prompt do
|
||||||
|
receive do
|
||||||
|
{_port, {:data, "> "}} -> :ok
|
||||||
|
{_port, {:data, _other}} ->
|
||||||
|
wait_for_prompt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
32
lib/beam_js/module.ex
Normal file
32
lib/beam_js/module.ex
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
defmodule BeamJs.Module do
|
||||||
|
import BeamJs.Utils
|
||||||
|
|
||||||
|
alias BeamJs.Function
|
||||||
|
|
||||||
|
def to_ast(module, opts \\ []) do
|
||||||
|
function_map =
|
||||||
|
module
|
||||||
|
|> BeamFile.byte_code()
|
||||||
|
|> ok(fn {_, _, _, _, _, sections} ->
|
||||||
|
{:ok, Enum.filter(sections, fn
|
||||||
|
{:function, _fun_name, _arity, _, _code} ->
|
||||||
|
true
|
||||||
|
_other ->
|
||||||
|
false
|
||||||
|
end)}
|
||||||
|
end)
|
||||||
|
|> ok(fn functions ->
|
||||||
|
Enum.map(functions, fn {:function, name, arity, _, code} ->
|
||||||
|
{"#{name}$#{arity}", Function.to_ast(code)}
|
||||||
|
end)
|
||||||
|
|> Map.new()
|
||||||
|
end)
|
||||||
|
|
||||||
|
case Keyword.get(opts, :only) do
|
||||||
|
nil ->
|
||||||
|
function_map
|
||||||
|
only ->
|
||||||
|
Map.take(function_map, only)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
4
lib/beam_js/utils.ex
Normal file
4
lib/beam_js/utils.ex
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule BeamJs.Utils do
|
||||||
|
def ok({:ok, value}, fun), do: fun.(value)
|
||||||
|
def ok(other, _fun), do: other
|
||||||
|
end
|
||||||
4
mix.exs
4
mix.exs
|
|
@ -6,11 +6,15 @@ defmodule BeamJs.MixProject do
|
||||||
app: :beam_js,
|
app: :beam_js,
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
elixir: "~> 1.19",
|
elixir: "~> 1.19",
|
||||||
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
deps: deps()
|
deps: deps()
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def elixirc_paths(:test), do: ["lib", "test/support"]
|
||||||
|
def elixirc_paths(_), do: ["lib"]
|
||||||
|
|
||||||
# Run "mix help compile.app" to learn about applications.
|
# Run "mix help compile.app" to learn about applications.
|
||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
|
|
|
||||||
3
mix.lock
3
mix.lock
|
|
@ -1,3 +1,6 @@
|
||||||
%{
|
%{
|
||||||
"beam_file": {:hex, :beam_file, "0.6.4", "73660f76330e6b29be2b8ae3779b30cb67a7803cde88a21e3e74371130eed4d4", [:mix], [], "hexpm", "3f295dba08a68360903e86be4f183d7fb70f762ee37ee176438dde23ea494431"},
|
"beam_file": {:hex, :beam_file, "0.6.4", "73660f76330e6b29be2b8ae3779b30cb67a7803cde88a21e3e74371130eed4d4", [:mix], [], "hexpm", "3f295dba08a68360903e86be4f183d7fb70f762ee37ee176438dde23ea494431"},
|
||||||
|
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||||
|
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||||
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
defmodule BeamJsTest do
|
defmodule BeamJsTest do
|
||||||
use ExUnit.Case
|
use BeamJs.JsCase
|
||||||
doctest BeamJs
|
doctest BeamJs
|
||||||
|
|
||||||
test "greets the world" do
|
test "can run JS code", %{js: js} do
|
||||||
assert BeamJs.hello() == :world
|
assert run(js, "1") == {:ok, 1}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "can encode and import", %{js: js} do
|
||||||
|
js_src =
|
||||||
|
Example
|
||||||
|
|> BeamJs.Module.to_ast(only: ["fact$1"])
|
||||||
|
|> BeamJs.Encoder.encode_module()
|
||||||
|
|
||||||
|
File.write!("tmp/example.mjs", js_src)
|
||||||
|
|
||||||
|
assert {:ok, [[_label, [["func_info", "Elixir.Example", "fact", 1]]]| _rest]} = run(js, ~s|const Example = await import('./tmp/example.mjs');JSON.stringify(Example.fact$1)|)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
5
test/support/example.ex
Normal file
5
test/support/example.ex
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule Example do
|
||||||
|
def fact(0), do: 1
|
||||||
|
def fact(1), do: 1
|
||||||
|
def fact(n), do: fact(n - 1) + fact(n - 2)
|
||||||
|
end
|
||||||
14
test/support/js_case.ex
Normal file
14
test/support/js_case.ex
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule BeamJs.JsCase do
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
import BeamJs.JsRuntime, only: [run: 2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup _tags do
|
||||||
|
{:ok, pid} = BeamJs.JsRuntime.start_link()
|
||||||
|
%{js: pid}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
ExUnit.start()
|
ExUnit.start()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue