Current section

Files

Jump to
dream_test src dream_test@reporter@json.erl
Raw

src/dream_test@reporter@json.erl

-module(dream_test@reporter@json).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/reporter/json.gleam").
-export([format/1, format_pretty/1, report/2, report_pretty/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" JSON test reporter for dream_test.\n"
"\n"
" This reporter outputs test results as JSON for CI/CD integration,\n"
" test aggregation, and tooling.\n"
"\n"
" ## Example Output\n"
"\n"
" ```json\n"
" {\n"
" \"version\": \"1.0\",\n"
" \"timestamp_ms\": 1733151045123,\n"
" \"duration_ms\": 315,\n"
" \"system\": {\n"
" \"os\": \"darwin\",\n"
" \"otp_version\": \"27\",\n"
" \"gleam_version\": \"0.67.0\"\n"
" },\n"
" \"summary\": {\n"
" \"total\": 3,\n"
" \"passed\": 2,\n"
" \"failed\": 1,\n"
" \"skipped\": 0,\n"
" \"pending\": 0,\n"
" \"timed_out\": 0,\n"
" \"setup_failed\": 0\n"
" },\n"
" \"tests\": [\n"
" {\n"
" \"name\": \"adds numbers\",\n"
" \"full_name\": [\"Calculator\", \"add\", \"adds numbers\"],\n"
" \"status\": \"passed\",\n"
" \"duration_ms\": 2,\n"
" \"tags\": [],\n"
" \"kind\": \"unit\",\n"
" \"failures\": []\n"
" }\n"
" ]\n"
" }\n"
" ```\n"
"\n"
" ## Usage\n"
"\n"
" ```gleam\n"
" import dream_test/reporter/json\n"
" import gleam/io\n"
"\n"
" pub fn main() {\n"
" to_test_cases(\"my_test\", tests())\n"
" |> run_all()\n"
" |> json.report(io.print)\n"
" |> exit_on_failure()\n"
" }\n"
" ```\n"
"\n"
" ## Combining with BDD Reporter\n"
"\n"
" ```gleam\n"
" results\n"
" |> bdd.report(io.print) // Human-readable to stdout\n"
" |> json.report(write_to_file) // JSON to file\n"
" |> exit_on_failure()\n"
" ```\n"
).
-file("src/dream_test/reporter/json.gleam", 233).
-spec build_payload_fields(
gleam@option:option(dream_test@types:failure_payload())
) -> list({binary(), gleam@json:json()}).
build_payload_fields(Payload) ->
case Payload of
none ->
[];
{some, {equality_failure, Actual, Expected}} ->
[{<<"expected"/utf8>>, gleam@json:string(Expected)},
{<<"actual"/utf8>>, gleam@json:string(Actual)}];
{some, {boolean_failure, Actual@1, Expected@1}} ->
[{<<"expected"/utf8>>, gleam@json:bool(Expected@1)},
{<<"actual"/utf8>>, gleam@json:bool(Actual@1)}];
{some, {option_failure, Actual@2, Expected_some}} ->
[{<<"expected_some"/utf8>>, gleam@json:bool(Expected_some)},
{<<"actual"/utf8>>, gleam@json:string(Actual@2)}];
{some, {result_failure, Actual@3, Expected_ok}} ->
[{<<"expected_ok"/utf8>>, gleam@json:bool(Expected_ok)},
{<<"actual"/utf8>>, gleam@json:string(Actual@3)}];
{some, {collection_failure, Actual@4, Expected@2, Operation}} ->
[{<<"expected"/utf8>>, gleam@json:string(Expected@2)},
{<<"actual"/utf8>>, gleam@json:string(Actual@4)},
{<<"operation"/utf8>>, gleam@json:string(Operation)}];
{some, {comparison_failure, Actual@5, Expected@3, Operator}} ->
[{<<"expected"/utf8>>, gleam@json:string(Expected@3)},
{<<"actual"/utf8>>, gleam@json:string(Actual@5)},
{<<"comparison_operator"/utf8>>, gleam@json:string(Operator)}];
{some, {string_match_failure, Actual@6, Pattern, Operation@1}} ->
[{<<"pattern"/utf8>>, gleam@json:string(Pattern)},
{<<"actual"/utf8>>, gleam@json:string(Actual@6)},
{<<"operation"/utf8>>, gleam@json:string(Operation@1)}];
{some,
{snapshot_failure, Actual@7, Expected@4, Snapshot_path, Is_missing}} ->
[{<<"expected"/utf8>>, gleam@json:string(Expected@4)},
{<<"actual"/utf8>>, gleam@json:string(Actual@7)},
{<<"snapshot_path"/utf8>>, gleam@json:string(Snapshot_path)},
{<<"is_missing"/utf8>>, gleam@json:bool(Is_missing)}];
{some, {custom_matcher_failure, Actual@8, Description}} ->
[{<<"actual"/utf8>>, gleam@json:string(Actual@8)},
{<<"description"/utf8>>, gleam@json:string(Description)}]
end.
-file("src/dream_test/reporter/json.gleam", 223).
-spec build_failure_json(dream_test@types:assertion_failure()) -> gleam@json:json().
build_failure_json(Failure) ->
Base_fields = [{<<"operator"/utf8>>,
gleam@json:string(erlang:element(2, Failure))},
{<<"message"/utf8>>, gleam@json:string(erlang:element(3, Failure))}],
Payload_fields = build_payload_fields(erlang:element(4, Failure)),
gleam@json:object(lists:append(Base_fields, Payload_fields)).
-file("src/dream_test/reporter/json.gleam", 286).
-spec status_to_string(dream_test@types:status()) -> binary().
status_to_string(Status) ->
case Status of
passed ->
<<"passed"/utf8>>;
failed ->
<<"failed"/utf8>>;
skipped ->
<<"skipped"/utf8>>;
pending ->
<<"pending"/utf8>>;
timed_out ->
<<"timed_out"/utf8>>;
setup_failed ->
<<"setup_failed"/utf8>>
end.
-file("src/dream_test/reporter/json.gleam", 297).
-spec kind_to_string(dream_test@types:test_kind()) -> binary().
kind_to_string(Kind) ->
case Kind of
unit ->
<<"unit"/utf8>>;
integration ->
<<"integration"/utf8>>;
{gherkin_scenario, Id} ->
<<"gherkin:"/utf8, Id/binary>>
end.
-file("src/dream_test/reporter/json.gleam", 211).
-spec build_test_json(dream_test@types:test_result()) -> gleam@json:json().
build_test_json(Result) ->
gleam@json:object(
[{<<"name"/utf8>>, gleam@json:string(erlang:element(2, Result))},
{<<"full_name"/utf8>>,
gleam@json:array(
erlang:element(3, Result),
fun gleam@json:string/1
)},
{<<"status"/utf8>>,
gleam@json:string(status_to_string(erlang:element(4, Result)))},
{<<"duration_ms"/utf8>>, gleam@json:int(erlang:element(5, Result))},
{<<"tags"/utf8>>,
gleam@json:array(
erlang:element(6, Result),
fun gleam@json:string/1
)},
{<<"kind"/utf8>>,
gleam@json:string(kind_to_string(erlang:element(8, Result)))},
{<<"failures"/utf8>>,
gleam@json:array(
erlang:element(7, Result),
fun build_failure_json/1
)}]
).
-file("src/dream_test/reporter/json.gleam", 305).
-spec count_by_status(
list(dream_test@types:test_result()),
dream_test@types:status()
) -> integer().
count_by_status(Results, Wanted) ->
gleam@list:fold(
Results,
0,
fun(Count, Result) -> case erlang:element(4, Result) =:= Wanted of
true ->
Count + 1;
false ->
Count
end end
).
-file("src/dream_test/reporter/json.gleam", 191).
-spec build_summary_json(list(dream_test@types:test_result())) -> gleam@json:json().
build_summary_json(Results) ->
Total = erlang:length(Results),
Passed = count_by_status(Results, passed),
Failed = count_by_status(Results, failed),
Skipped = count_by_status(Results, skipped),
Pending = count_by_status(Results, pending),
Timed_out = count_by_status(Results, timed_out),
Setup_failed = count_by_status(Results, setup_failed),
gleam@json:object(
[{<<"total"/utf8>>, gleam@json:int(Total)},
{<<"passed"/utf8>>, gleam@json:int(Passed)},
{<<"failed"/utf8>>, gleam@json:int(Failed)},
{<<"skipped"/utf8>>, gleam@json:int(Skipped)},
{<<"pending"/utf8>>, gleam@json:int(Pending)},
{<<"timed_out"/utf8>>, gleam@json:int(Timed_out)},
{<<"setup_failed"/utf8>>, gleam@json:int(Setup_failed)}]
).
-file("src/dream_test/reporter/json.gleam", 314).
-spec calculate_total_duration(list(dream_test@types:test_result())) -> integer().
calculate_total_duration(Results) ->
gleam@list:fold(
Results,
0,
fun(Total, Result) -> Total + erlang:element(5, Result) end
).
-file("src/dream_test/reporter/json.gleam", 383).
-spec spaces(integer()) -> binary().
spaces(Count) ->
gleam@string:repeat(<<" "/utf8>>, Count).
-file("src/dream_test/reporter/json.gleam", 326).
-spec prettify_chars(list(binary()), integer(), boolean(), binary()) -> binary().
prettify_chars(Chars, Indent, In_string, Acc) ->
case Chars of
[] ->
Acc;
[<<"\\"/utf8>>, Escaped | Rest] when In_string ->
prettify_chars(
Rest,
Indent,
true,
<<<<Acc/binary, "\\"/utf8>>/binary, Escaped/binary>>
);
[<<"\""/utf8>> | Rest@1] ->
prettify_chars(
Rest@1,
Indent,
not In_string,
<<Acc/binary, "\""/utf8>>
);
[Char | Rest@2] when In_string ->
prettify_chars(Rest@2, Indent, true, <<Acc/binary, Char/binary>>);
[<<"{"/utf8>> | Rest@3] ->
New_indent = Indent + 2,
prettify_chars(
Rest@3,
New_indent,
false,
<<<<Acc/binary, "{\n"/utf8>>/binary,
(spaces(New_indent))/binary>>
);
[<<"}"/utf8>> | Rest@4] ->
New_indent@1 = Indent - 2,
prettify_chars(
Rest@4,
New_indent@1,
false,
<<<<<<Acc/binary, "\n"/utf8>>/binary,
(spaces(New_indent@1))/binary>>/binary,
"}"/utf8>>
);
[<<"["/utf8>> | Rest@5] ->
New_indent@2 = Indent + 2,
prettify_chars(
Rest@5,
New_indent@2,
false,
<<<<Acc/binary, "[\n"/utf8>>/binary,
(spaces(New_indent@2))/binary>>
);
[<<"]"/utf8>> | Rest@6] ->
New_indent@3 = Indent - 2,
prettify_chars(
Rest@6,
New_indent@3,
false,
<<<<<<Acc/binary, "\n"/utf8>>/binary,
(spaces(New_indent@3))/binary>>/binary,
"]"/utf8>>
);
[<<","/utf8>> | Rest@7] ->
prettify_chars(
Rest@7,
Indent,
false,
<<<<Acc/binary, ",\n"/utf8>>/binary, (spaces(Indent))/binary>>
);
[<<":"/utf8>> | Rest@8] ->
prettify_chars(Rest@8, Indent, false, <<Acc/binary, ": "/utf8>>);
[<<" "/utf8>> | Rest@9] ->
prettify_chars(Rest@9, Indent, false, Acc);
[Char@1 | Rest@10] ->
prettify_chars(
Rest@10,
Indent,
false,
<<Acc/binary, Char@1/binary>>
)
end.
-file("src/dream_test/reporter/json.gleam", 322).
-spec prettify_json(binary()) -> binary().
prettify_json(Input) ->
prettify_chars(gleam@string:to_graphemes(Input), 0, false, <<""/utf8>>).
-file("src/dream_test/reporter/json.gleam", 183).
-spec build_system_json() -> gleam@json:json().
build_system_json() ->
gleam@json:object(
[{<<"os"/utf8>>,
gleam@json:string(dream_test_reporter_json_ffi:get_os())},
{<<"otp_version"/utf8>>,
gleam@json:string(
dream_test_reporter_json_ffi:get_otp_version()
)},
{<<"gleam_version"/utf8>>,
gleam@json:string(
dream_test_reporter_json_ffi:get_gleam_version()
)}]
).
-file("src/dream_test/reporter/json.gleam", 170).
-spec build_report_json(list(dream_test@types:test_result())) -> gleam@json:json().
build_report_json(Results) ->
Total_duration = calculate_total_duration(Results),
gleam@json:object(
[{<<"version"/utf8>>, gleam@json:string(<<"1.0"/utf8>>)},
{<<"timestamp_ms"/utf8>>,
gleam@json:int(dream_test_reporter_json_ffi:get_timestamp_ms())},
{<<"duration_ms"/utf8>>, gleam@json:int(Total_duration)},
{<<"system"/utf8>>, build_system_json()},
{<<"summary"/utf8>>, build_summary_json(Results)},
{<<"tests"/utf8>>, gleam@json:array(Results, fun build_test_json/1)}]
).
-file("src/dream_test/reporter/json.gleam", 91).
?DOC(
" Format test results as a compact JSON string.\n"
"\n"
" Returns a single-line JSON string suitable for machine parsing.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let json_string = format(results)\n"
" file.write(\"test-results.json\", json_string)\n"
" ```\n"
).
-spec format(list(dream_test@types:test_result())) -> binary().
format(Results) ->
_pipe = build_report_json(Results),
gleam@json:to_string(_pipe).
-file("src/dream_test/reporter/json.gleam", 107).
?DOC(
" Format test results as pretty-printed JSON.\n"
"\n"
" Returns an indented, human-readable JSON string with 2-space indentation.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let json_string = format_pretty(results)\n"
" io.println(json_string)\n"
" ```\n"
).
-spec format_pretty(list(dream_test@types:test_result())) -> binary().
format_pretty(Results) ->
_pipe = build_report_json(Results),
_pipe@1 = gleam@json:to_string(_pipe),
prettify_json(_pipe@1).
-file("src/dream_test/reporter/json.gleam", 140).
?DOC(
" Print test results as JSON using a provided writer function.\n"
"\n"
" This is the main entry point for JSON reporting. The writer function\n"
" receives the JSON string and can print it, log it, or write it to a file.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" // Print to stdout\n"
" results |> json.report(io.print)\n"
"\n"
" // Write to file\n"
" results |> json.report(fn(s) { file.write(\"results.json\", s) })\n"
" ```\n"
"\n"
" ## Returns\n"
"\n"
" Returns the input results unchanged, enabling pipeline composition:\n"
"\n"
" ```gleam\n"
" to_test_cases(\"my_test\", tests())\n"
" |> run_all()\n"
" |> bdd.report(io.print)\n"
" |> json.report(write_to_file)\n"
" |> exit_on_failure()\n"
" ```\n"
).
-spec report(list(dream_test@types:test_result()), fun((binary()) -> nil)) -> list(dream_test@types:test_result()).
report(Results, Write) ->
Write(format(Results)),
Results.
-file("src/dream_test/reporter/json.gleam", 158).
?DOC(
" Print test results as pretty-printed JSON using a provided writer function.\n"
"\n"
" Same as `report` but with indented, human-readable output.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" results |> json.report_pretty(io.print)\n"
" ```\n"
).
-spec report_pretty(
list(dream_test@types:test_result()),
fun((binary()) -> nil)
) -> list(dream_test@types:test_result()).
report_pretty(Results, Write) ->
Write(format_pretty(Results)),
Results.