aboutsummaryrefslogtreecommitdiffstats
path: root/test/integration/beam
diff options
context:
space:
mode:
authorMarcos Magueta <maguetamarcos@gmail.com>2026-10-31 02:28:17 -0300
committerMarcos Magueta <maguetamarcos@gmail.com>2026-10-31 02:28:17 -0300
commit71856adc5ff5d64c7be1b37854fc8a205d55e482 (patch)
tree25770cf98154c0da696878228783ac8faecde08b /test/integration/beam
parente76cc6f862656a3ada9fa3d4418bd060df937885 (diff)
downloadRNT-71856adc5ff5d64c7be1b37854fc8a205d55e482.tar.gz
RNT-71856adc5ff5d64c7be1b37854fc8a205d55e482.zip
update tests and add beam tests
Diffstat (limited to 'test/integration/beam')
-rw-r--r--test/integration/beam/library.erl19
-rw-r--r--test/integration/beam/rnt.erl97
-rw-r--r--test/integration/beam/shelf.erl22
3 files changed, 138 insertions, 0 deletions
diff --git a/test/integration/beam/library.erl b/test/integration/beam/library.erl
new file mode 100644
index 0000000..b9186ab
--- /dev/null
+++ b/test/integration/beam/library.erl
@@ -0,0 +1,19 @@
+-module(library).
+-export([books/0]).
+
+books() ->
+ from([book(<<"Structure and Interpretation of Computer Programs">>, <<"Harold Abelson">>),
+ book(<<"The Art of Computer Programming">>, <<"Donald Knuth">>),
+ book(<<"An Introduction to Database Systems">>, <<"C. J. Date">>),
+ book(<<"Less Than Words Can Say">>, <<"Richard Mitchell">>),
+ book(<<"The Graves of Academe">>, <<"Richard Mitchell">>),
+ book(<<"The Leaning Tower of Babel">>, <<"Richard Mitchell">>),
+ book(<<"The Gift of Fire">>, <<"Richard Mitchell">>)]).
+
+book(Title, Author) -> {<<"book">>, #{<<"title">> => Title, <<"author">> => Author}}.
+
+from(Books) ->
+ fun(Limit) ->
+ {Now, Later} = lists:split(min(Limit, length(Books)), Books),
+ {Now, case Later of [] -> done; _ -> from(Later) end}
+ end.
diff --git a/test/integration/beam/rnt.erl b/test/integration/beam/rnt.erl
new file mode 100644
index 0000000..702be2d
--- /dev/null
+++ b/test/integration/beam/rnt.erl
@@ -0,0 +1,97 @@
+-module(rnt).
+-export([start/0, define/3, invoke/1, evaluate/2]).
+
+start() ->
+ logger:remove_handler(default),
+ logger:add_handler(default, logger_std_h, #{config => #{type => standard_error}}),
+ put(port, open_port({fd, 0, 1}, [binary, {packet, 4}, eof])),
+ loop().
+
+loop() -> serve(frame()), loop().
+
+frame() ->
+ Port = get(port),
+ receive
+ {Port, {data, Frame}} -> Frame;
+ {Port, eof} -> halt()
+ end.
+
+send(Frame) -> port_command(get(port), Frame).
+
+answering(F) ->
+ try F()
+ catch Class:Reason -> [$e, io_lib:format("~p:~p", [Class, Reason])]
+ end.
+
+%% What RNT asks of the node.
+serve(<<$h>>) ->
+ send(answering(fun() ->
+ [M:rnt_init() || {M, _} <- code:all_loaded(),
+ erlang:function_exported(M, rnt_init, 0)],
+ <<$o>>
+ end));
+serve(<<$i, Id:64, M:16, Module:M/binary, F:16, Function:F/binary, B:32, Beam:B/binary>>) ->
+ put({poll, Id},
+ fun(Limit) ->
+ Name = load(binary_to_atom(Module), Beam),
+ (apply(Name, binary_to_existing_atom(Function), []))(Limit)
+ end);
+serve(<<$f, Id:64, Limit:32>>) ->
+ send(answering(fun() -> fetch(Id, Limit) end));
+serve(<<$c, Id:64>>) ->
+ erase({poll, Id}).
+
+load(Name, Beam) ->
+ {ok, {Name, Md5}} = beam_lib:md5(Beam),
+ case erlang:module_loaded(Name) andalso Name:module_info(md5) =:= Md5 of
+ true -> Name;
+ false ->
+ code:purge(Name),
+ {module, Name} = code:load_binary(Name, atom_to_list(Name) ++ ".beam", Beam),
+ Name
+ end.
+
+fetch(Id, Limit) ->
+ case get({poll, Id}) of
+ done -> batch([], true);
+ Relation ->
+ {Tuples, Rest} = Relation(Limit),
+ put({poll, Id}, Rest),
+ batch(Tuples, Rest =:= done)
+ end.
+
+batch(Tuples, Exhausted) ->
+ [$b, case Exhausted of true -> 1; false -> 0 end
+ | [[<<(byte_size(Term)):32>>, Term] || Tuple <- Tuples, Term <- [term_to_binary(Tuple)]]].
+
+%% What the node asks of RNT.
+define(Path, Evaluator, Source) ->
+ request([$D, path(Path), sized(Evaluator), Source]).
+
+invoke(Path) ->
+ relation(request([$X, path(Path)])).
+
+evaluate(Evaluator, Source) ->
+ relation(request([$E, sized(Evaluator), Source])).
+
+relation(Id) ->
+ fun(Limit) ->
+ {Tuples, Exhausted} = request(<<$N, Id:64, Limit:32>>),
+ {Tuples, case Exhausted of true -> done; false -> relation(Id) end}
+ end.
+
+request(Frame) -> send(Frame), reply().
+
+reply() ->
+ case frame() of
+ <<$o>> -> ok;
+ <<$r, Id:64>> -> Id;
+ <<$b, Exhausted, Tuples/binary>> ->
+ {[binary_to_term(T) || <<N:32, T:N/binary>> <= Tuples], Exhausted =:= 1};
+ <<$e, Message/binary>> -> error({rnt, Message});
+ Frame -> serve(Frame), reply()
+ end.
+
+path(Segments) -> [<<(length(Segments)):16>> | [sized(S) || S <- Segments]].
+
+sized(Bytes) -> <<(byte_size(Bytes)):16, Bytes/binary>>.
diff --git a/test/integration/beam/shelf.erl b/test/integration/beam/shelf.erl
new file mode 100644
index 0000000..a9b6823
--- /dev/null
+++ b/test/integration/beam/shelf.erl
@@ -0,0 +1,22 @@
+-module(shelf).
+-export([rnt_init/0, titles/0, authors/0]).
+
+-define(TITLES, [<<"karuta">>, <<"program">>, <<"titles">>]).
+-define(BOOKS, {$b, [<<"karuta">>, <<"program">>, <<"books">>]}).
+
+%% A FOL program over a beam program RNT holds, defined from the node.
+rnt_init() ->
+ rnt:define(?TITLES, <<"fol">>, project(?BOOKS, [<<"title">>])).
+
+titles() -> rnt:invoke(?TITLES).
+
+authors() -> rnt:evaluate(<<"fol">>, project(?BOOKS, [<<"author">>])).
+
+project(Term, Attributes) ->
+ bencode({$p, #{<<"term">> => Term, <<"attributes">> => Attributes}}).
+
+bencode(Bytes) when is_binary(Bytes) -> [integer_to_binary(byte_size(Bytes)), $:, Bytes];
+bencode(List) when is_list(List) -> [$l, [bencode(X) || X <- List], $e];
+bencode(Map) when is_map(Map) ->
+ [$d, [[bencode(K), bencode(V)] || {K, V} <- lists:sort(maps:to_list(Map))], $e];
+bencode({Tag, Value}) -> [$t, Tag, bencode(Value)].