Current section
Files
Jump to
Current section
Files
src/exercism_test_runner@internal.erl
-module(exercism_test_runner@internal).
-compile([no_auto_import, nowarn_unused_vars]).
-export([extract_function_body/3, print_error/3, print_summary/1, run_test_function/1, append_output/1, clear_output/0, get_output/0, run_test/1, results_to_json/1]).
-export_type([error/0, suite/0, test/0, test_result/0, process_dictionary_key/0]).
-type error() :: {unequal, gleam@dynamic:dynamic_(), gleam@dynamic:dynamic_()} |
{todo, binary()} |
{panic, binary()} |
{unmatched, gleam@dynamic:dynamic_(), integer()} |
{unmatched_case, gleam@dynamic:dynamic_()} |
{crashed, gleam@dynamic:dynamic_()}.
-type suite() :: {suite, binary(), binary(), list(test())}.
-type test() :: {test,
binary(),
binary(),
fun(() -> {ok, nil} | {error, error()}),
binary()}.
-type test_result() :: {test_result,
test(),
gleam@option:option(error()),
binary()}.
-type process_dictionary_key() :: execism_test_runner_user_output.
-spec drop_function_header(binary()) -> binary().
drop_function_header(Src) ->
case gleam@string:pop_grapheme(Src) of
{ok, {<<"{"/utf8>>, Src@1}} ->
Src@1;
{ok, {_, Src@2}} ->
drop_function_header(Src@2);
{error, _} ->
Src
end.
-spec undent(binary()) -> binary().
undent(Line) ->
case gleam@string:starts_with(Line, <<" "/utf8>>) of
true ->
gleam@string:drop_left(Line, 2);
false ->
Line
end.
-spec extract_function_body(binary(), integer(), integer()) -> binary().
extract_function_body(Src, Start, End) ->
_pipe = Src,
_pipe@1 = gleam@bit_string:from_string(_pipe),
_pipe@2 = gleam@bit_string:slice(_pipe@1, Start, End - Start),
_pipe@3 = gleam@result:unwrap(_pipe@2, <<>>),
_pipe@4 = gleam@bit_string:to_string(_pipe@3),
_pipe@5 = gleam@result:unwrap(_pipe@4, <<""/utf8>>),
_pipe@6 = gleam@string:drop_right(_pipe@5, 1),
_pipe@7 = drop_function_header(_pipe@6),
_pipe@8 = gleam@string:trim(_pipe@7),
_pipe@9 = gleam@string:split(_pipe@8, <<"\n"/utf8>>),
_pipe@10 = gleam@list:map(_pipe@9, fun undent/1),
gleam@string:join(_pipe@10, <<"\n"/utf8>>).
-spec print_properties(binary(), list({binary(), binary()})) -> binary().
print_properties(Header, Properties) ->
_pipe = Properties,
_pipe@1 = gleam@list:map(
_pipe,
fun(Pair) ->
Key = <<(gleam@string:pad_left(
erlang:element(1, Pair),
7,
<<" "/utf8>>
))/binary,
": "/utf8>>,
<<(gleam_community@ansi:cyan(Key))/binary,
(erlang:element(2, Pair))/binary>>
end
),
_pipe@2 = gleam@list:prepend(_pipe@1, Header),
gleam@string:join(_pipe@2, <<"\n"/utf8>>).
-spec print_error(error(), binary(), binary()) -> binary().
print_error(Error, Path, Test_name) ->
case Error of
{unequal, Left, Right} ->
Diff = gap:to_styled(
gap:compare_strings(
gleam@string:inspect(Left),
gleam@string:inspect(Right)
)
),
_pipe = Path,
print_properties(
_pipe,
[{<<"test"/utf8>>, Test_name},
{<<"error"/utf8>>, <<"left != right"/utf8>>},
{<<"left"/utf8>>, erlang:element(2, Diff)},
{<<"right"/utf8>>, erlang:element(3, Diff)}]
);
{todo, Message} ->
print_properties(
Path,
[{<<"test"/utf8>>, Test_name},
{<<"error"/utf8>>, <<"todo"/utf8>>},
{<<"info"/utf8>>, Message}]
);
{panic, Message@1} ->
print_properties(
Path,
[{<<"test"/utf8>>, Test_name},
{<<"error"/utf8>>, <<"panic"/utf8>>},
{<<"info"/utf8>>, Message@1}]
);
{unmatched, Value, Line} ->
print_properties(
<<<<Path/binary, ":"/utf8>>/binary,
(gleam@int:to_string(Line))/binary>>,
[{<<"test"/utf8>>, Test_name},
{<<"error"/utf8>>, <<"Pattern match failed"/utf8>>},
{<<"value"/utf8>>, gleam@string:inspect(Value)}]
);
{unmatched_case, Value@1} ->
print_properties(
Path,
[{<<"test"/utf8>>, Test_name},
{<<"error"/utf8>>, <<"Pattern match failed"/utf8>>},
{<<"value"/utf8>>, gleam@string:inspect(Value@1)}]
);
{crashed, Error@1} ->
print_properties(
Path,
[{<<"test"/utf8>>, Test_name},
{<<"error"/utf8>>, <<"Program crashed"/utf8>>},
{<<"cause"/utf8>>, gleam@string:inspect(Error@1)}]
)
end.
-spec print_summary(list(test_result())) -> {boolean(), binary()}.
print_summary(Results) ->
Total = begin
_pipe = Results,
_pipe@1 = gleam@list:length(_pipe),
gleam@int:to_string(_pipe@1)
end,
Failed = begin
_pipe@2 = Results,
_pipe@3 = gleam@list:filter(
_pipe@2,
fun(Result) -> erlang:element(3, Result) /= none end
),
_pipe@4 = gleam@list:length(_pipe@3),
gleam@int:to_string(_pipe@4)
end,
Colour = case Failed of
<<"0"/utf8>> ->
fun gleam_community@ansi:green/1;
_ ->
fun gleam_community@ansi:red/1
end,
Message = Colour(
<<<<<<<<"Ran "/utf8, Total/binary>>/binary, " tests, "/utf8>>/binary,
Failed/binary>>/binary,
" failed"/utf8>>
),
{Failed =:= <<"0"/utf8>>, Message}.
-spec decode_tag(any(), gleam@dynamic:dynamic_()) -> {ok, nil} |
{error, list(gleam@dynamic:decode_error())}.
decode_tag(Tag, Data) ->
case gleam@dynamic:from(Tag) =:= Data of
true ->
{ok, nil};
false ->
{error,
[{decode_error,
<<"Tag"/utf8>>,
gleam@dynamic:classify(Data),
[]}]}
end.
-spec decode_pattern_match_failed_error(gleam@dynamic:dynamic_()) -> {ok,
error()} |
{error, list(gleam@dynamic:decode_error())}.
decode_pattern_match_failed_error(Error) ->
Decoder = gleam@dynamic:decode3(
fun(_, Value, Line) -> {unmatched, Value, Line} end,
gleam@dynamic:field(
erlang:binary_to_atom(<<"gleam_error"/utf8>>),
fun(_capture) ->
decode_tag(
erlang:binary_to_atom(<<"let_assert"/utf8>>),
_capture
)
end
),
gleam@dynamic:field(
erlang:binary_to_atom(<<"value"/utf8>>),
fun(Field@0) -> {ok, Field@0} end
),
gleam@dynamic:field(
erlang:binary_to_atom(<<"line"/utf8>>),
fun gleam@dynamic:int/1
)
),
Decoder(Error).
-spec decode_todo_error(gleam@dynamic:dynamic_()) -> {ok, error()} |
{error, list(gleam@dynamic:decode_error())}.
decode_todo_error(Error) ->
Decoder = gleam@dynamic:decode2(
fun(_, Message) -> {todo, Message} end,
gleam@dynamic:field(
erlang:binary_to_atom(<<"gleam_error"/utf8>>),
fun(_capture) ->
decode_tag(erlang:binary_to_atom(<<"todo"/utf8>>), _capture)
end
),
gleam@dynamic:field(
erlang:binary_to_atom(<<"message"/utf8>>),
fun gleam@dynamic:string/1
)
),
Decoder(Error).
-spec decode_panic_error(gleam@dynamic:dynamic_()) -> {ok, error()} |
{error, list(gleam@dynamic:decode_error())}.
decode_panic_error(Error) ->
Decoder = gleam@dynamic:decode2(
fun(_, Message) -> {panic, Message} end,
gleam@dynamic:field(
erlang:binary_to_atom(<<"gleam_error"/utf8>>),
fun(_capture) ->
decode_tag(erlang:binary_to_atom(<<"panic"/utf8>>), _capture)
end
),
gleam@dynamic:field(
erlang:binary_to_atom(<<"message"/utf8>>),
fun gleam@dynamic:string/1
)
),
Decoder(Error).
-spec decode_case_clause_error(gleam@dynamic:dynamic_()) -> {ok, error()} |
{error, list(gleam@dynamic:decode_error())}.
decode_case_clause_error(Error) ->
Decoder = gleam@dynamic:decode2(
fun(_, Value) -> {unmatched_case, Value} end,
gleam@dynamic:element(
0,
fun(_capture) ->
decode_tag(
erlang:binary_to_atom(<<"case_clause"/utf8>>),
_capture
)
end
),
gleam@dynamic:element(1, fun(Field@0) -> {ok, Field@0} end)
),
Decoder(Error).
-spec decode_unequal_error(gleam@dynamic:dynamic_()) -> {ok, error()} |
{error, list(gleam@dynamic:decode_error())}.
decode_unequal_error(Error) ->
Tag = erlang:binary_to_atom(<<"unequal"/utf8>>),
Decoder = gleam@dynamic:decode3(
fun(_, A, B) -> {unequal, A, B} end,
gleam@dynamic:element(0, fun(_capture) -> decode_tag(Tag, _capture) end),
gleam@dynamic:element(1, fun(Field@0) -> {ok, Field@0} end),
gleam@dynamic:element(2, fun(Field@0) -> {ok, Field@0} end)
),
Decoder(Error).
-spec convert_error(gleam@erlang:crash()) -> error().
convert_error(Error) ->
case Error of
{exited, Error@1} ->
{crashed, Error@1};
{thrown, Error@2} ->
{crashed, Error@2};
{errored, Error@3} ->
Decoders = [fun decode_unequal_error/1,
fun decode_pattern_match_failed_error/1,
fun decode_case_clause_error/1,
fun decode_todo_error/1,
fun decode_panic_error/1],
_pipe = Error@3,
_pipe@1 = (gleam@dynamic:any(Decoders))(_pipe),
gleam@result:unwrap(_pipe@1, {crashed, Error@3})
end.
-spec run_test_function(fun(() -> any())) -> {ok, nil} | {error, error()}.
run_test_function(Function) ->
_pipe = Function,
_pipe@1 = gleam_erlang_ffi:rescue(_pipe),
_pipe@2 = gleam@result:map_error(_pipe@1, fun convert_error/1),
gleam@result:replace(_pipe@2, nil).
-spec append_output(binary()) -> nil.
append_output(Value) ->
_pipe = execism_test_runner_user_output,
_pipe@1 = erlang:get(_pipe),
_pipe@2 = gleam@dynamic:string(_pipe@1),
_pipe@3 = gleam@result:unwrap(_pipe@2, <<""/utf8>>),
_pipe@4 = gleam@string:append(_pipe@3, Value),
_pipe@5 = gleam@string:append(_pipe@4, <<"\n"/utf8>>),
erlang:put(execism_test_runner_user_output, _pipe@5),
nil.
-spec clear_output() -> nil.
clear_output() ->
erlang:put(execism_test_runner_user_output, <<""/utf8>>),
nil.
-spec get_output() -> binary().
get_output() ->
_pipe = execism_test_runner_user_output,
_pipe@1 = erlang:get(_pipe),
_pipe@2 = gleam@dynamic:string(_pipe@1),
gleam@result:unwrap(_pipe@2, <<""/utf8>>).
-spec run_test(test()) -> test_result().
run_test(Test) ->
clear_output(),
Error@1 = case (erlang:element(4, Test))() of
{ok, _} ->
none;
{error, Error} ->
{some, Error}
end,
{test_result, Test, Error@1, get_output()}.
-spec truncate(binary()) -> binary().
truncate(Output) ->
case gleam@string:length(Output) > 500 of
true ->
_pipe = Output,
_pipe@1 = gleam@string:slice(_pipe, 0, 448),
_pipe@2 = gleam@string:append(_pipe@1, <<"...\n\n"/utf8>>),
gleam@string:append(
_pipe@2,
<<"Output was truncated. Please limit to 500 chars"/utf8>>
);
false ->
Output
end.
-spec test_result_json(test_result()) -> gleam@json:json().
test_result_json(Result) ->
Fields = case erlang:element(3, Result) of
{some, Error} ->
Error@1 = print_error(
Error,
erlang:element(3, erlang:element(2, Result)),
erlang:element(2, erlang:element(2, Result))
),
[{<<"status"/utf8>>, gleam@json:string(<<"fail"/utf8>>)},
{<<"message"/utf8>>, gleam@json:string(Error@1)}];
none ->
[{<<"status"/utf8>>, gleam@json:string(<<"pass"/utf8>>)}]
end,
Fields@1 = case truncate(erlang:element(4, Result)) of
<<""/utf8>> ->
Fields;
Output ->
[{<<"output"/utf8>>, gleam@json:string(Output)} | Fields]
end,
Fields@2 = [{<<"name"/utf8>>,
gleam@json:string(erlang:element(2, erlang:element(2, Result)))},
{<<"test_code"/utf8>>,
gleam@json:string(erlang:element(5, erlang:element(2, Result)))} |
Fields@1],
gleam@json:object(Fields@2).
-spec results_to_json(list(test_result())) -> binary().
results_to_json(Results) ->
Failed = gleam@list:any(
Results,
fun(Test) -> erlang:element(3, Test) /= none end
),
Status = case Failed of
true ->
<<"fail"/utf8>>;
false ->
<<"pass"/utf8>>
end,
_pipe = gleam@json:object(
[{<<"version"/utf8>>, gleam@json:int(2)},
{<<"status"/utf8>>, gleam@json:string(Status)},
{<<"tests"/utf8>>,
gleam@json:array(Results, fun test_result_json/1)}]
),
gleam@json:to_string(_pipe).