-module(exercism_test_runner@internal). -compile([no_auto_import, nowarn_unused_vars]). -export([print_error/3, print_summary/1, run_test_function/1, results_to_json/1, clear_output/0, append_output/1, get_output/0, run_test/1, extract_function_body/3]). -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 undent(binary()) -> binary(). undent(Line) -> case gleam@string:starts_with(Line, <<" "/utf8>>) of true -> gleam@string:drop_left(Line, 2); false -> Line end. -spec print_error(error(), binary(), binary()) -> binary(). print_error(Error, Path, Test_name) -> case Error of {unequal, Left, Right} -> Diff = begin _pipe = gap:compare_strings( gleam@string:inspect(Left), gleam@string:inspect(Right) ), gap:to_styled(_pipe) end, gleam@string:join( [Path, <<" test: "/utf8, Test_name/binary>>, <<" error: left != right"/utf8>>, <<" left: "/utf8, (erlang:element(2, Diff))/binary>>, <<" right: "/utf8, (erlang:element(3, Diff))/binary>>], <<"\n"/utf8>> ); {todo, Message} -> gleam@string:join( [Path, <<" test: "/utf8, Test_name/binary>>, <<" error: todo"/utf8>>, <<" info: "/utf8, Message/binary>>], <<"\n"/utf8>> ); {panic, Message@1} -> gleam@string:join( [Path, <<" test: "/utf8, Test_name/binary>>, <<" error: panic"/utf8>>, <<" info: "/utf8, Message@1/binary>>], <<"\n"/utf8>> ); {unmatched, Value, Line} -> gleam@string:join( [<<<>/binary, (gleam@int:to_string(Line))/binary>>, <<" test: "/utf8, Test_name/binary>>, <<" error: Pattern match failed"/utf8>>, <<" value: "/utf8, (gleam@string:inspect(Value))/binary>>], <<"\n"/utf8>> ); {unmatched_case, Value@1} -> gleam@string:join( [Path, <<" test: "/utf8, Test_name/binary>>, <<" error: Pattern match failed"/utf8>>, <<" value: "/utf8, (gleam@string:inspect(Value@1))/binary>>], <<"\n"/utf8>> ); {crashed, Error@1} -> gleam@string:join( [Path, <<" test: "/utf8, Test_name/binary>>, <<" error: Program crashed"/utf8>>, <<" cause: "/utf8, (gleam@string:inspect(Error@1))/binary>>], <<"\n"/utf8>> ) 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, Message = <<<<<<<<"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(<<"Assertion pattern match failed"/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 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). -spec clear_output() -> nil. clear_output() -> erlang:put(execism_test_runner_user_output, <<""/utf8>>), 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 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 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 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>>).