-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( <<<>/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).