this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Test result reporting

+282 -580
+2 -1
src/gleeunit/internal/gleam_panic.gleam
··· 2 2 3 3 pub type GleamPanic { 4 4 GleamPanic( 5 + message: String, 6 + file: String, 5 7 module: String, 6 8 function: String, 7 9 line: Int, 8 - message: String, 9 10 kind: PanicKind, 10 11 ) 11 12 }
+4 -3
src/gleeunit/internal/gleam_panic_ffi.erl
··· 39 39 40 40 wrap(#{ 41 41 gleam_error := _, 42 + file := File, 43 + message := Message, 42 44 module := Module, 43 45 function := Function, 44 - line := Line, 45 - message := Message 46 + line := Line 46 47 }, Kind) -> 47 - {ok, {gleam_panic, Module, Function, Line, Message, Kind}}. 48 + {ok, {gleam_panic, Message, File, Module, Function, Line, Kind}}.
+3 -1
src/gleeunit/internal/gleam_panic_ffi.mjs
··· 84 84 } 85 85 86 86 function wrap(e, kind) { 87 - return new Ok(new GleamPanic(e.module, e.function, e.line, e.message, kind)); 87 + return new Ok( 88 + new GleamPanic(e.message, e.file, e.module, e.function, e.line, kind), 89 + ); 88 90 }
+207
src/gleeunit/internal/reporting.gleam
··· 1 + import gleam/bit_array 2 + import gleam/dynamic 3 + import gleam/int 4 + import gleam/io 5 + import gleam/option.{type Option} 6 + import gleam/result 7 + import gleam/string 8 + import gleeunit/internal/gleam_panic.{type GleamPanic} 9 + 10 + pub type State { 11 + State(passed: Int, failed: Int, skipped: Int) 12 + } 13 + 14 + pub fn new_state() -> State { 15 + State(passed: 0, failed: 0, skipped: 0) 16 + } 17 + 18 + pub fn finished(state: State) -> Int { 19 + case state { 20 + State(passed: 0, failed: 0, skipped: 0) -> { 21 + io.println("\nNo tests found!") 22 + 1 23 + } 24 + State(failed: 0, skipped: 0, ..) -> { 25 + let message = "\n" <> int.to_string(state.passed) <> " tests, no failures" 26 + io.println(green(message)) 27 + 0 28 + } 29 + State(skipped: 0, ..) -> { 30 + let message = 31 + "\n" 32 + <> int.to_string(state.passed) 33 + <> " tests, " 34 + <> int.to_string(state.failed) 35 + <> " failures" 36 + io.println(red(message)) 37 + 0 38 + } 39 + State(failed: 0, ..) -> { 40 + let message = 41 + "\n" 42 + <> int.to_string(state.passed) 43 + <> " tests, 0 failures, " 44 + <> int.to_string(state.skipped) 45 + <> " skipped" 46 + io.println(yellow(message)) 47 + 1 48 + } 49 + State(..) -> { 50 + let message = 51 + "\n" 52 + <> int.to_string(state.passed) 53 + <> " tests, " 54 + <> int.to_string(state.failed) 55 + <> " failures, " 56 + <> " skipped" 57 + io.println(red(message)) 58 + 1 59 + } 60 + } 61 + } 62 + 63 + pub fn test_passed(state: State) -> State { 64 + io.print(green(".")) 65 + State(..state, passed: state.passed + 1) 66 + } 67 + 68 + pub fn test_failed( 69 + state: State, 70 + module: String, 71 + function: String, 72 + error: dynamic.Dynamic, 73 + ) -> State { 74 + let message = case gleam_panic.from_dynamic(error) { 75 + Ok(error) -> { 76 + let src = option.from_result(read_file(error.file)) 77 + format_gleam_error(error, module, function, src) 78 + } 79 + Error(_) -> format_unknown(error) 80 + } 81 + 82 + io.print("\n" <> message) 83 + State(..state, failed: state.failed + 1) 84 + } 85 + 86 + fn format_unknown(error: dynamic.Dynamic) -> String { 87 + "\nAn unexpected error occurred:\n\n" <> string.inspect(error) 88 + } 89 + 90 + fn format_gleam_error( 91 + error: GleamPanic, 92 + module: String, 93 + function: String, 94 + src: Option(BitArray), 95 + ) -> String { 96 + let location = grey(error.file <> ":" <> int.to_string(error.line)) 97 + 98 + case error.kind { 99 + gleam_panic.Panic -> { 100 + string.concat([ 101 + bold(red("panic")) <> " " <> location <> "\n", 102 + blue(" test") <> ": " <> module <> "." <> function <> "\n", 103 + blue(" info") <> ": " <> error.message <> "\n", 104 + ]) 105 + } 106 + 107 + gleam_panic.Todo -> { 108 + string.concat([ 109 + bold(yellow("todo")) <> " " <> location <> "\n", 110 + blue(" test") <> ": " <> module <> "." <> function <> "\n", 111 + blue(" info") <> ": " <> error.message <> "\n", 112 + ]) 113 + } 114 + 115 + gleam_panic.Assert(start:, expression_end:, kind:, ..) -> { 116 + string.concat([ 117 + bold(red("assert")) <> " " <> location <> "\n", 118 + blue(" test") <> ": " <> module <> "." <> function <> "\n", 119 + code_snippet(src, start, expression_end), 120 + assert_info(kind), 121 + blue(" info") <> ": " <> error.message <> "\n", 122 + ]) 123 + } 124 + 125 + // TODO: include the whole expression 126 + gleam_panic.LetAssert(start:, pattern_end:, value:, ..) -> { 127 + string.concat([ 128 + bold(red("let assert")) <> " " <> location <> "\n", 129 + blue(" test") <> ": " <> module <> "." <> function <> "\n", 130 + code_snippet(src, start, pattern_end), 131 + blue("value") <> ": " <> string.inspect(value) <> "\n", 132 + blue(" info") <> ": " <> error.message <> "\n", 133 + ]) 134 + } 135 + } 136 + } 137 + 138 + fn assert_info(kind: gleam_panic.AssertKind) -> String { 139 + case kind { 140 + gleam_panic.BinaryOperator(operator:, left:, right:) -> 141 + string.concat([assert_value(" left", left), assert_value("right", right)]) 142 + 143 + gleam_panic.FunctionCall(arguments:) -> todo 144 + 145 + gleam_panic.OtherExpression(expression:) -> "" 146 + } 147 + } 148 + 149 + fn assert_value(name: String, value: gleam_panic.AssertedExpression) -> String { 150 + case value.kind { 151 + gleam_panic.Expression(value:) -> 152 + blue(name) <> ": " <> string.inspect(value) <> "\n" 153 + 154 + gleam_panic.Literal(..) | gleam_panic.Unevaluated -> "" 155 + } 156 + } 157 + 158 + fn code_snippet(src: Option(BitArray), start: Int, end: Int) -> String { 159 + { 160 + use src <- result.try(option.to_result(src, Nil)) 161 + use snippet <- result.try(bit_array.slice(src, start, end - start)) 162 + use snippet <- result.try(bit_array.to_string(snippet)) 163 + let snippet = blue(" code") <> ": " <> snippet <> "\n" 164 + Ok(snippet) 165 + } 166 + |> result.unwrap("") 167 + } 168 + 169 + pub fn test_skipped(state: State, module: String, function: String) -> State { 170 + io.print("\n" <> module <> "." <> function <> yellow(" skipped")) 171 + State(..state, skipped: state.skipped + 1) 172 + } 173 + 174 + fn bold(text: String) -> String { 175 + "\u{001b}[1m" <> text <> "\u{001b}[22m" 176 + } 177 + 178 + fn blue(text: String) -> String { 179 + "\u{001b}[34m" <> text <> "\u{001b}[39m" 180 + } 181 + 182 + fn yellow(text: String) -> String { 183 + "\u{001b}[33m" <> text <> "\u{001b}[39m" 184 + } 185 + 186 + fn green(text: String) -> String { 187 + "\u{001b}[32m" <> text <> "\u{001b}[39m" 188 + } 189 + 190 + fn red(text: String) -> String { 191 + "\u{001b}[31m" <> text <> "\u{001b}[39m" 192 + } 193 + 194 + fn grey(text: String) -> String { 195 + "\u{001b}[90m" <> text <> "\u{001b}[39m" 196 + } 197 + 198 + @external(erlang, "file", "read_file") 199 + fn read_file(path: String) -> Result(BitArray, dynamic.Dynamic) { 200 + case read_file_text(path) { 201 + Ok(text) -> Ok(bit_array.from_string(text)) 202 + Error(e) -> Error(e) 203 + } 204 + } 205 + 206 + @external(javascript, "../../gleeunit_ffi.mjs", "read_file") 207 + fn read_file_text(path: String) -> Result(String, dynamic.Dynamic)
-1
src/gleeunit_ffi.erl
··· 9 9 Results = filelib:wildcard(binary_to_list(Pattern), binary_to_list(In)), 10 10 lists:map(fun list_to_binary/1, Results). 11 11 12 - 13 12 should_equal(Actual, Expected) -> 14 13 ?assertEqual(Expected, Actual), 15 14 nil.
+11
src/gleeunit_ffi.mjs
··· 1 + import { readFileSync } from "fs"; 2 + import { Ok, Error as GleamError } from "./gleam.mjs"; 3 + 4 + export function read_file(path) { 5 + try { 6 + return new Ok(readFileSync(path)); 7 + } catch { 8 + return new GleamError(undefined); 9 + } 10 + } 11 + 1 12 async function* gleamFiles(directory) { 2 13 for (let entry of await read_dir(directory)) { 3 14 let path = join_path(directory, entry);
+40 -574
src/gleeunit_progress.erl
··· 1 1 %% A formatter adapted from Sean Cribb's https://github.com/seancribbs/eunit_formatters 2 2 3 - %% @doc A listener/reporter for eunit that prints '.' for each 4 - %% success, 'F' for each failure, and 'E' for each error. It can also 5 - %% optionally summarize the failures at the end. 6 - -compile({nowarn_unused_function, [insert/2, to_list/1, to_list/2, size/1]}). 7 3 -module(gleeunit_progress). 8 4 -behaviour(eunit_listener). 9 5 -define(NOTEST, true). 10 6 -include_lib("eunit/include/eunit.hrl"). 11 7 12 - -define(RED, "\e[0;31m"). 13 - -define(GREEN, "\e[0;32m"). 14 - -define(YELLOW, "\e[0;33m"). 15 - -define(WHITE, "\e[0;37m"). 16 - -define(CYAN, "\e[0;36m"). 17 - -define(RESET, "\e[0m"). 18 - 19 - -record(node,{ 20 - rank = 0 :: non_neg_integer(), 21 - key :: term(), 22 - value :: term(), 23 - children = new() :: binomial_heap() 24 - }). 25 - 26 - -export_type([binomial_heap/0, heap_node/0]). 27 - -type binomial_heap() :: [ heap_node() ]. 28 - -type heap_node() :: #node{}. 29 - 30 8 %% eunit_listener callbacks 31 9 -export([ 32 - init/1, 33 - handle_begin/3, 34 - handle_end/3, 35 - handle_cancel/3, 36 - terminate/2, 37 - start/0, 38 - start/1 39 - ]). 40 - 41 - %% -- binomial_heap.erl content start -- 42 - 43 - -record(state, { 44 - status = dict:new() :: euf_dict(), 45 - failures = [] :: [[pos_integer()]], 46 - skips = [] :: [[pos_integer()]], 47 - timings = new() :: binomial_heap(), 48 - colored = true :: boolean(), 49 - profile = false :: boolean() 50 - }). 51 - 52 - -type euf_dict() :: dict:dict(). 53 - 54 - -spec new() -> binomial_heap(). 55 - new() -> 56 - []. 57 - 58 - % Inserts a new pair into the heap (or creates a new heap) 59 - -spec insert(term(), term()) -> binomial_heap(). 60 - insert(Key,Value) -> 61 - insert(Key,Value,[]). 62 - 63 - -spec insert(term(), term(), binomial_heap()) -> binomial_heap(). 64 - insert(Key,Value,Forest) -> 65 - insTree(#node{key=Key,value=Value},Forest). 66 - 67 - % Merges two heaps 68 - -spec merge(binomial_heap(), binomial_heap()) -> binomial_heap(). 69 - merge(TS1,[]) when is_list(TS1) -> TS1; 70 - merge([],TS2) when is_list(TS2) -> TS2; 71 - merge([#node{rank=R1}=T1|TS1]=F1,[#node{rank=R2}=T2|TS2]=F2) -> 72 - if 73 - R1 < R2 -> 74 - [T1 | merge(TS1,F2)]; 75 - R2 < R1 -> 76 - [T2 | merge(F1, TS2)]; 77 - true -> 78 - insTree(link(T1,T2),merge(TS1,TS2)) 79 - end. 80 - 81 - % Deletes the top entry from the heap and returns it 82 - -spec delete(binomial_heap()) -> {{term(), term()}, binomial_heap()}. 83 - delete(TS) -> 84 - {#node{key=Key,value=Value,children=TS1},TS2} = getMin(TS), 85 - {{Key,Value},merge(lists:reverse(TS1),TS2)}. 86 - 87 - % Turns the heap into list in heap order 88 - -spec to_list(binomial_heap()) -> [{term(), term()}]. 89 - to_list([]) -> []; 90 - to_list(List) when is_list(List) -> 91 - to_list([],List). 92 - to_list(Acc, []) -> 93 - lists:reverse(Acc); 94 - to_list(Acc,Forest) -> 95 - {Next, Trees} = delete(Forest), 96 - to_list([Next|Acc], Trees). 10 + init/1, handle_begin/3, handle_end/3, handle_cancel/3, terminate/2, 11 + start/0, start/1 12 + ]). 97 13 98 - % Take N elements from the top of the heap 99 - -spec take(non_neg_integer(), binomial_heap()) -> [{term(), term()}]. 100 - take(N,Trees) when is_integer(N), is_list(Trees) -> 101 - take(N,Trees,[]). 102 - take(0,_Trees,Acc) -> 103 - lists:reverse(Acc); 104 - take(_N,[],Acc)-> 105 - lists:reverse(Acc); 106 - take(N,Trees,Acc) -> 107 - {Top,T2} = delete(Trees), 108 - take(N-1,T2,[Top|Acc]). 14 + -define(reporting, gleeunit@internal@reporting). 109 15 110 - % Get an estimate of the size based on the binomial property 111 - -spec size(binomial_heap()) -> non_neg_integer(). 112 - size(Forest) -> 113 - erlang:trunc(lists:sum([math:pow(2,R) || #node{rank=R} <- Forest])). 114 - 115 - %% Private API 116 - -spec link(heap_node(), heap_node()) -> heap_node(). 117 - link(#node{rank=R,key=X1,children=C1}=T1,#node{key=X2,children=C2}=T2) -> 118 - case X1 < X2 of 119 - true -> 120 - T1#node{rank=R+1,children=[T2|C1]}; 121 - _ -> 122 - T2#node{rank=R+1,children=[T1|C2]} 123 - end. 124 - 125 - insTree(Tree, []) -> 126 - [Tree]; 127 - insTree(#node{rank=R1}=T1, [#node{rank=R2}=T2|Rest] = TS) -> 128 - case R1 < R2 of 129 - true -> 130 - [T1|TS]; 131 - _ -> 132 - insTree(link(T1,T2),Rest) 133 - end. 134 - 135 - getMin([T]) -> 136 - {T,[]}; 137 - getMin([#node{key=K} = T|TS]) -> 138 - {#node{key=K1} = T1,TS1} = getMin(TS), 139 - case K < K1 of 140 - true -> {T,TS}; 141 - _ -> {T1,[T|TS1]} 142 - end. 143 - 144 - %% -- binomial_heap.erl content end -- 145 - 146 - %% Startup 147 16 start() -> 148 17 start([]). 149 18 150 19 start(Options) -> 151 20 eunit_listener:start(?MODULE, Options). 152 21 153 - %%------------------------------------------ 154 - %% eunit_listener callbacks 155 - %%------------------------------------------ 156 - init(Options) -> 157 - #state{colored=proplists:get_bool(colored, Options), 158 - profile=proplists:get_bool(profile, Options)}. 22 + init(_Options) -> 23 + ?reporting:new_state(). 159 24 160 - handle_begin(group, Data, St) -> 161 - GID = proplists:get_value(id, Data), 162 - Dict = St#state.status, 163 - St#state{status=dict:store(GID, orddict:from_list([{type, group}|Data]), Dict)}; 164 - handle_begin(test, Data, St) -> 165 - TID = proplists:get_value(id, Data), 166 - Dict = St#state.status, 167 - St#state{status=dict:store(TID, orddict:from_list([{type, test}|Data]), Dict)}. 25 + handle_begin(_test_or_group, _data, State) -> 26 + State. 168 27 169 - handle_end(group, Data, St) -> 170 - St#state{status=merge_on_end(Data, St#state.status)}; 171 - handle_end(test, Data, St) -> 172 - NewStatus = merge_on_end(Data, St#state.status), 173 - St1 = print_progress(Data, St), 174 - St2 = record_timing(Data, St1), 175 - St2#state{status=NewStatus}. 28 + handle_end(group, _data, State) -> 29 + State; 30 + handle_end(test, Data, State) -> 31 + {AtomModule, AtomFunction, _Arity} = proplists:get_value(source, Data), 32 + Module = erlang:atom_to_binary(AtomModule), 33 + Function = erlang:atom_to_binary(AtomFunction), 176 34 177 - handle_cancel(_, Data, #state{status=Status, skips=Skips}=St) -> 178 - Status1 = merge_on_end(Data, Status), 179 - ID = proplists:get_value(id, Data), 180 - St#state{status=Status1, skips=[ID|Skips]}. 35 + % EUnit swallows stdout, so print it to make debugging easier. 36 + case proplists:get_value(output, Data) of 37 + undefined -> ok; 38 + <<>> -> ok; 39 + Out -> gleam@io:print(Out) 40 + end, 181 41 182 - terminate({ok, Data}, St) -> 183 - print_failures(St), 184 - print_pending(St), 185 - print_profile(St), 186 - print_timing(St), 187 - print_results(Data, St); 188 - terminate({error, Reason}, St) -> 189 - io:nl(), io:nl(), 190 - print_colored(io_lib:format("Eunit failed: ~25p~n", [Reason]), ?RED, St), 191 - sync_end(error). 192 - 193 - sync_end(Result) -> 194 - receive 195 - {stop, Reference, ReplyTo} -> 196 - ReplyTo ! {result, Reference, Result}, 197 - ok 198 - end. 199 - 200 - %%------------------------------------------ 201 - %% Print and collect information during run 202 - %%------------------------------------------ 203 - print_progress(Data, St) -> 204 - TID = proplists:get_value(id, Data), 205 42 case proplists:get_value(status, Data) of 206 43 ok -> 207 - print_progress_success(St), 208 - St; 44 + ?reporting:test_passed(State); 209 45 {skipped, _Reason} -> 210 - print_progress_skipped(St), 211 - St#state{skips=[TID|St#state.skips]}; 212 - {error, Exception} -> 213 - print_progress_failed(Exception, St), 214 - St#state{failures=[TID|St#state.failures]} 46 + ?reporting:test_skipped(State, Module, Function); 47 + {error, {_, Exception, _Stack}} -> 48 + ?reporting:test_failed(State, Module, Function, Exception) 215 49 end. 216 50 217 - record_timing(Data, State=#state{timings=T, profile=true}) -> 218 - TID = proplists:get_value(id, Data), 219 - case lists:keyfind(time, 1, Data) of 220 - {time, Int} -> 221 - %% It's a min-heap, so we insert negative numbers instead 222 - %% of the actuals and normalize when we report on them. 223 - T1 = insert(-Int, TID, T), 224 - State#state{timings=T1}; 225 - false -> 226 - State 227 - end; 228 - record_timing(_Data, State) -> 51 + handle_cancel(_test_or_group, _data, State) -> 229 52 State. 230 53 231 - print_progress_success(St) -> 232 - print_colored(".", ?GREEN, St). 233 - 234 - print_progress_skipped(St) -> 235 - print_colored("*", ?YELLOW, St). 236 - 237 - print_progress_failed(_Exc, St) -> 238 - print_colored("F", ?RED, St). 239 - 240 - merge_on_end(Data, Dict) -> 241 - ID = proplists:get_value(id, Data), 242 - dict:update(ID, 243 - fun(Old) -> 244 - orddict:merge(fun merge_data/3, Old, orddict:from_list(Data)) 245 - end, Dict). 246 - 247 - merge_data(_K, undefined, X) -> X; 248 - merge_data(_K, X, undefined) -> X; 249 - merge_data(_K, _, X) -> X. 250 - 251 - %%------------------------------------------ 252 - %% Print information at end of run 253 - %%------------------------------------------ 254 - print_failures(#state{failures=[]}) -> 255 - ok; 256 - print_failures(#state{failures=Fails}=State) -> 257 - io:nl(), 258 - io:fwrite("Failures:~n",[]), 259 - lists:foldr(print_failure_fun(State), 1, Fails), 260 - ok. 261 - 262 - print_failure_fun(#state{status=Status}=State) -> 263 - fun(Key, Count) -> 264 - TestData = dict:fetch(Key, Status), 265 - TestId = format_test_identifier(TestData), 266 - io:fwrite("~n ~p) ~ts~n", [Count, TestId]), 267 - print_failure_reason(proplists:get_value(status, TestData), 268 - proplists:get_value(output, TestData), 269 - State), 270 - io:nl(), 271 - Count + 1 272 - end. 273 - 274 - print_gleam_location(#{function := Function, line := Line, module := Module }, State) -> 275 - X = indent(5, "location: ~s.~s:~p~n", [Module, Function, Line]), 276 - print_colored(X, ?CYAN, State); 277 - print_gleam_location(_, _) -> 278 - ok. 279 - 280 - inspect(X) -> 281 - gleam@string:inspect(X). 282 - 283 - print_gleam_failure_reason( 284 - #{gleam_error := let_assert, message := Message, value := Value}, 285 - State 286 - ) -> 287 - print_colored(indent(5, "~s~n", [Message]), ?RED, State), 288 - print_colored(indent(5, " value: ", []), ?RED, State), 289 - print_colored(indent(0, "~ts~n", [inspect(Value)]), ?RESET, State); 290 - print_gleam_failure_reason( 291 - #{gleam_error := todo, message := Message}, 292 - State 293 - ) -> 294 - print_colored(indent(5, "todo expression run~n", []), ?RED, State), 295 - print_colored(indent(5, " message: ", []), ?RED, State), 296 - print_colored(indent(0, "~s~n", [Message]), ?RESET, State); 297 - print_gleam_failure_reason(Error, State) -> 298 - print_colored(indent(5, "~p~n", [Error]), ?RED, State). 299 - 300 - % New Gleeunit specific formatters 301 - print_failure_reason( 302 - {error, {error, #{gleam_error := _} = Error, Stack}}, Output, State 303 - ) when is_list(Stack) -> 304 - print_gleam_failure_reason(Error, State), 305 - print_gleam_location(Error, State), 306 - print_stack(Stack, State), 307 - print_failure_output(5, Output, State); 308 - print_failure_reason({error, {error, {case_clause, Value}, Stack}}, Output, State) when is_list(Stack) -> 309 - print_colored(indent(5, "No case clause matched~n", []), ?RED, State), 310 - print_colored(indent(5, "Value: ", []), ?CYAN, State), 311 - print_colored(indent(0, "~ts~n", [inspect(Value)]), ?RESET, State), 312 - print_stack(Stack, State), 313 - print_failure_output(5, Output, State); 314 - % From the original Erlang version 315 - print_failure_reason({skipped, Reason}, _Output, State) -> 316 - print_colored(io_lib:format(" ~ts~n", [format_pending_reason(Reason)]), 317 - ?RED, State); 318 - print_failure_reason({error, {_Class, Term, _}}, Output, State) when 319 - is_tuple(Term), tuple_size(Term) == 2, is_list(element(2, Term)) -> 320 - print_assertion_failure(Term, State), 321 - print_failure_output(5, Output, State); 322 - print_failure_reason({error, {error, Error, Stack}}, Output, State) when is_list(Stack) -> 323 - print_colored(indent(5, "Failure: ~p~n", [Error]), ?RED, State), 324 - print_stack(Stack, State), 325 - print_failure_output(5, Output, State); 326 - print_failure_reason({error, Reason}, Output, State) -> 327 - print_colored(indent(5, "Failure: ~p~n", [Reason]), ?RED, State), 328 - print_failure_output(5, Output, State). 329 - 330 - gleam_format_module_name(Module) -> 331 - string:replace(atom_to_list(Module), "@", "/", all). 332 - 333 - print_stack(Stack, State) -> 334 - print_colored(indent(5, "stacktrace:~n", []), ?CYAN, State), 335 - print_stackframes(Stack, State). 336 - print_stackframes([{eunit_test, _, _, _} | Stack], State) -> 337 - print_stackframes(Stack, State); 338 - print_stackframes([{eunit_proc, _, _, _} | Stack], State) -> 339 - print_stackframes(Stack, State); 340 - print_stackframes([{Module, Function, _Arity, _Location} | Stack], State) -> 341 - GleamModule = gleam_format_module_name(Module), 342 - print_colored(indent(7, "~s.~p~n", [GleamModule, Function]), ?CYAN, State), 343 - print_stackframes(Stack, State); 344 - print_stackframes([], _State) -> 345 - ok. 346 - 347 - 348 - print_failure_output(_, <<>>, _) -> ok; 349 - print_failure_output(_, undefined, _) -> ok; 350 - print_failure_output(Indent, Output, State) -> 351 - print_colored(indent(Indent, "output: ~ts", [Output]), ?CYAN, State). 352 - 353 - print_assertion_failure({Type, Props}, State) -> 354 - FailureDesc = format_assertion_failure(Type, Props, 5), 355 - print_colored(FailureDesc, ?RED, State), 356 - io:nl(). 357 - 358 - print_pending(#state{skips=[]}) -> 54 + terminate({ok, _Data}, State) -> 55 + ?reporting:finished(State), 359 56 ok; 360 - print_pending(#state{status=Status, skips=Skips}=State) -> 361 - io:nl(), 362 - io:fwrite("Pending:~n", []), 363 - lists:foreach(fun(ID) -> 364 - Info = dict:fetch(ID, Status), 365 - case proplists:get_value(reason, Info) of 366 - undefined -> 367 - ok; 368 - Reason -> 369 - print_pending_reason(Reason, Info, State) 370 - end 371 - end, lists:reverse(Skips)), 372 - io:nl(). 373 - 374 - print_pending_reason(Reason0, Data, State) -> 375 - Text = case proplists:get_value(type, Data) of 376 - group -> 377 - io_lib:format(" ~ts~n", [proplists:get_value(desc, Data)]); 378 - test -> 379 - io_lib:format(" ~ts~n", [format_test_identifier(Data)]) 380 - end, 381 - Reason = io_lib:format(" %% ~ts~n", [format_pending_reason(Reason0)]), 382 - print_colored(Text, ?YELLOW, State), 383 - print_colored(Reason, ?CYAN, State). 384 - 385 - print_profile(#state{timings=T, status=Status, profile=true}=State) -> 386 - TopN = take(10, T), 387 - TopNTime = abs(lists:sum([ Time || {Time, _} <- TopN ])), 388 - TLG = dict:fetch([], Status), 389 - TotalTime = proplists:get_value(time, TLG), 390 - if TotalTime =/= undefined andalso TotalTime > 0 andalso TopN =/= [] -> 391 - TopNPct = (TopNTime / TotalTime) * 100, 392 - io:nl(), io:nl(), 393 - io:fwrite("Top ~p slowest tests (~ts, ~.1f% of total time):", [length(TopN), format_time(TopNTime), TopNPct]), 394 - lists:foreach(print_timing_fun(State), TopN), 395 - io:nl(); 396 - true -> ok 397 - end; 398 - print_profile(#state{profile=false}) -> 399 - ok. 57 + terminate({error, Reason}, State) -> 58 + ?reporting:finished(State), 59 + io:fwrite(" 60 + Eunit failed: 400 61 401 - print_timing(#state{status=Status}) -> 402 - TLG = dict:fetch([], Status), 403 - Time = proplists:get_value(time, TLG), 404 - io:nl(), 405 - io:fwrite("Finished in ~ts~n", [format_time(Time)]), 406 - ok. 62 + ~80p 407 63 408 - print_results(Data, State) -> 409 - Pass = proplists:get_value(pass, Data, 0), 410 - Fail = proplists:get_value(fail, Data, 0), 411 - Skip = proplists:get_value(skip, Data, 0), 412 - Cancel = proplists:get_value(cancel, Data, 0), 413 - Total = Pass + Fail + Skip + Cancel, 414 - {Color, Result} = if Fail > 0 -> {?RED, error}; 415 - Skip > 0; Cancel > 0 -> {?YELLOW, error}; 416 - Pass =:= 0 -> {?YELLOW, ok}; 417 - true -> {?GREEN, ok} 418 - end, 419 - print_results(Color, Total, Fail, Skip, Cancel, State), 420 - sync_end(Result). 64 + This is probably a bug in gleeunit. Please report it. 65 + ", [Reason]), 66 + sync_end(error). 421 67 422 - print_results(Color, 0, _, _, _, State) -> 423 - print_colored("0 tests\n", Color, State); 424 - print_results(Color, Total, Fail, Skip, Cancel, State) -> 425 - SkipText = format_optional_result(Skip, "skipped"), 426 - CancelText = format_optional_result(Cancel, "cancelled"), 427 - Text = io_lib:format("~p tests, ~p failures~ts~ts~n", [Total, Fail, SkipText, CancelText]), 428 - print_colored(Text, Color, State). 429 - 430 - print_timing_fun(#state{status=Status}=State) -> 431 - fun({Time, Key}) -> 432 - TestData = dict:fetch(Key, Status), 433 - TestId = format_test_identifier(TestData), 434 - io:nl(), 435 - io:fwrite(" ~ts~n", [TestId]), 436 - print_colored([" "|format_time(abs(Time))], ?CYAN, State) 68 + sync_end(Result) -> 69 + receive 70 + {stop, Reference, ReplyTo} -> 71 + ReplyTo ! {result, Reference, Result}, 72 + ok 437 73 end. 438 - 439 - %%------------------------------------------ 440 - %% Print to the console with the given color 441 - %% if enabled. 442 - %%------------------------------------------ 443 - print_colored(Text, Color, #state{colored=true}) -> 444 - io:fwrite("~s~ts~s", [Color, Text, ?RESET]); 445 - print_colored(Text, _Color, #state{colored=false}) -> 446 - io:fwrite("~ts", [Text]). 447 - 448 - %%------------------------------------------ 449 - %% Generic data formatters 450 - %%------------------------------------------ 451 - format_function_name(M, F) -> 452 - M1 = gleam_format_module_name(M), 453 - io_lib:format("~ts.~ts", [M1, F]). 454 - 455 - format_optional_result(0, _) -> 456 - []; 457 - format_optional_result(Count, Text) -> 458 - io_lib:format(", ~p ~ts", [Count, Text]). 459 - 460 - format_test_identifier(Data) -> 461 - {Mod, Fun, _} = proplists:get_value(source, Data), 462 - Line = case proplists:get_value(line, Data) of 463 - 0 -> ""; 464 - L -> io_lib:format(":~p", [L]) 465 - end, 466 - Desc = case proplists:get_value(desc, Data) of 467 - undefined -> ""; 468 - DescText -> io_lib:format(": ~ts", [DescText]) 469 - end, 470 - io_lib:format("~ts~ts~ts", [format_function_name(Mod, Fun), Line, Desc]). 471 - 472 - format_time(undefined) -> 473 - "? seconds"; 474 - format_time(Time) -> 475 - io_lib:format("~.3f seconds", [Time / 1000]). 476 - 477 - format_pending_reason({module_not_found, M}) -> 478 - M1 = gleam_format_module_name(M), 479 - io_lib:format("Module '~ts' missing", [M1]); 480 - format_pending_reason({no_such_function, {M,F,_}}) -> 481 - M1 = gleam_format_module_name(M), 482 - io_lib:format("Function ~ts undefined", [format_function_name(M1,F)]); 483 - format_pending_reason({exit, Reason}) -> 484 - io_lib:format("Related process exited with reason: ~p", [Reason]); 485 - format_pending_reason(Reason) -> 486 - io_lib:format("Unknown error: ~p", [Reason]). 487 - 488 - %% @doc Formats all the known eunit assertions, you're on your own if 489 - %% you make an assertion yourself. 490 - format_assertion_failure(Type, Props, I) when Type =:= assertion_failed 491 - ; Type =:= assert -> 492 - Keys = proplists:get_keys(Props), 493 - HasEUnitProps = ([expression, value] -- Keys) =:= [], 494 - HasHamcrestProps = ([expected, actual, matcher] -- Keys) =:= [], 495 - if 496 - HasEUnitProps -> 497 - [indent(I, "Failure: ?assert(~ts)~n", [proplists:get_value(expression, Props)]), 498 - indent(I, " expected: true~n", []), 499 - case proplists:get_value(value, Props) of 500 - false -> 501 - indent(I, " got: false", []); 502 - {not_a_boolean, V} -> 503 - indent(I, " got: ~p", [V]) 504 - end]; 505 - HasHamcrestProps -> 506 - [indent(I, "Failure: ?assertThat(~p)~n", [proplists:get_value(matcher, Props)]), 507 - indent(I, " expected: ~ts~n", [inspect(proplists:get_value(expected, Props))]), 508 - indent(I, " got: ~ts", [inspect(proplists:get_value(actual, Props))])]; 509 - true -> 510 - [indent(I, "Failure: unknown assert: ~p", [Props])] 511 - end; 512 - 513 - format_assertion_failure(Type, Props, I) when Type =:= assertMatch_failed 514 - ; Type =:= assertMatch -> 515 - Expr = proplists:get_value(expression, Props), 516 - Pattern = proplists:get_value(pattern, Props), 517 - Value = proplists:get_value(value, Props), 518 - [indent(I, "Failure: ?assertMatch(~ts, ~ts)~n", [Pattern, Expr]), 519 - indent(I, " expected: = ~ts~n", [Pattern]), 520 - indent(I, " got: ~p", [Value])]; 521 - 522 - format_assertion_failure(Type, Props, I) when Type =:= assertNotMatch_failed 523 - ; Type =:= assertNotMatch -> 524 - Expr = proplists:get_value(expression, Props), 525 - Pattern = proplists:get_value(pattern, Props), 526 - Value = proplists:get_value(value, Props), 527 - [indent(I, "Failure: ?assertNotMatch(~ts, ~ts)~n", [Pattern, Expr]), 528 - indent(I, " expected not: = ~ts~n", [Pattern]), 529 - indent(I, " got: ~p", [Value])]; 530 - 531 - format_assertion_failure(Type, Props, I) when Type =:= assertEqual_failed 532 - ; Type =:= assertEqual -> 533 - Expected = inspect(proplists:get_value(expected, Props)), 534 - Value = inspect(proplists:get_value(value, Props)), 535 - [indent(I, "Values were not equal~n", []), 536 - indent(I, "expected: ~ts~n", [Expected]), 537 - indent(I, " got: ~ts", [Value])]; 538 - 539 - format_assertion_failure(Type, Props, I) when Type =:= assertNotEqual_failed 540 - ; Type =:= assertNotEqual -> 541 - Value = inspect(proplists:get_value(value, Props)), 542 - [indent(I, "Values were equal~n", []), 543 - indent(I, "expected: not ~ts~n,", [Value]), 544 - indent(I, " got: ~ts", [Value])]; 545 - 546 - format_assertion_failure(Type, Props, I) when Type =:= assertException_failed 547 - ; Type =:= assertException -> 548 - Expr = proplists:get_value(expression, Props), 549 - Pattern = proplists:get_value(pattern, Props), 550 - {Class, Term} = extract_exception_pattern(Pattern), % I hate that we have to do this, why not just give DATA 551 - [indent(I, "Failure: ?assertException(~ts, ~ts, ~ts)~n", [Class, Term, Expr]), 552 - case proplists:is_defined(unexpected_success, Props) of 553 - true -> 554 - [indent(I, " expected: exception ~ts but nothing was raised~n", [Pattern]), 555 - indent(I, " got: value ~p", [proplists:get_value(unexpected_success, Props)])]; 556 - false -> 557 - Ex = proplists:get_value(unexpected_exception, Props), 558 - [indent(I, " expected: exception ~ts~n", [Pattern]), 559 - indent(I, " got: exception ~p", [Ex])] 560 - end]; 561 - 562 - format_assertion_failure(Type, Props, I) when Type =:= assertNotException_failed 563 - ; Type =:= assertNotException -> 564 - Expr = proplists:get_value(expression, Props), 565 - Pattern = proplists:get_value(pattern, Props), 566 - {Class, Term} = extract_exception_pattern(Pattern), % I hate that we have to do this, why not just give DAT 567 - Ex = proplists:get_value(unexpected_exception, Props), 568 - [indent(I, "Failure: ?assertNotException(~ts, ~ts, ~ts)~n", [Class, Term, Expr]), 569 - indent(I, " expected not: exception ~ts~n", [Pattern]), 570 - indent(I, " got: exception ~p", [Ex])]; 571 - 572 - format_assertion_failure(Type, Props, I) when Type =:= command_failed 573 - ; Type =:= command -> 574 - Cmd = proplists:get_value(command, Props), 575 - Expected = proplists:get_value(expected_status, Props), 576 - Status = proplists:get_value(status, Props), 577 - [indent(I, "Failure: ?cmdStatus(~p, ~p)~n", [Expected, Cmd]), 578 - indent(I, " expected: status ~p~n", [Expected]), 579 - indent(I, " got: status ~p", [Status])]; 580 - 581 - format_assertion_failure(Type, Props, I) when Type =:= assertCmd_failed 582 - ; Type =:= assertCmd -> 583 - Cmd = proplists:get_value(command, Props), 584 - Expected = proplists:get_value(expected_status, Props), 585 - Status = proplists:get_value(status, Props), 586 - [indent(I, "Failure: ?assertCmdStatus(~p, ~p)~n", [Expected, Cmd]), 587 - indent(I, " expected: status ~p~n", [Expected]), 588 - indent(I, " got: status ~p", [Status])]; 589 - 590 - format_assertion_failure(Type, Props, I) when Type =:= assertCmdOutput_failed 591 - ; Type =:= assertCmdOutput -> 592 - Cmd = proplists:get_value(command, Props), 593 - Expected = proplists:get_value(expected_output, Props), 594 - Output = proplists:get_value(output, Props), 595 - [indent(I, "Failure: ?assertCmdOutput(~p, ~p)~n", [Expected, Cmd]), 596 - indent(I, " expected: ~p~n", [Expected]), 597 - indent(I, " got: ~p", [Output])]; 598 - 599 - format_assertion_failure(Type, Props, I) -> 600 - indent(I, "~p", [{Type, Props}]). 601 - 602 - indent(I, Fmt, Args) -> 603 - io_lib:format("~" ++ integer_to_list(I) ++ "s" ++ Fmt, [" "|Args]). 604 - 605 - extract_exception_pattern(Str) -> 606 - ["{", Class, Term|_] = re:split(Str, "[, ]{1,2}", [unicode,{return,list}]), 607 - {Class, Term}.
+15
test/gleam_panics_test.gleam
··· 10 10 fn rescue(f: fn() -> t) -> Result(t, dynamic.Dynamic) 11 11 12 12 pub fn panic_test() { 13 + panic 13 14 let assert Error(e) = rescue(fn() { panic }) 14 15 let assert Ok(e) = gleam_panic.from_dynamic(e) 16 + assert e.file == "test/gleam_panics_test.gleam" 15 17 assert e.kind == Panic 16 18 assert e.function == "panic_test" 17 19 assert e.module == "gleam_panics_test" ··· 20 22 } 21 23 22 24 pub fn panic_message_test() { 25 + todo 23 26 let assert Error(e) = rescue(fn() { panic as "oh my!" }) 24 27 let assert Ok(e) = gleam_panic.from_dynamic(e) 28 + assert e.file == "test/gleam_panics_test.gleam" 25 29 assert e.kind == Panic 26 30 assert e.function == "panic_message_test" 27 31 assert e.module == "gleam_panics_test" ··· 32 36 pub fn todo_test() { 33 37 let assert Error(e) = rescue(fn() { todo }) 34 38 let assert Ok(e) = gleam_panic.from_dynamic(e) 39 + assert e.file == "test/gleam_panics_test.gleam" 35 40 assert e.kind == Todo 36 41 assert e.function == "todo_test" 37 42 assert e.module == "gleam_panics_test" ··· 41 46 } 42 47 43 48 pub fn todo_message_test() { 49 + let get_names = fn() { ["Lucy"] } 50 + assert get_names() == ["Lucy", "Nubi"] 44 51 let assert Error(e) = rescue(fn() { todo as "oh my!" }) 45 52 let assert Ok(e) = gleam_panic.from_dynamic(e) 53 + assert e.file == "test/gleam_panics_test.gleam" 46 54 assert e.kind == Todo 47 55 assert e.function == "todo_message_test" 48 56 assert e.module == "gleam_panics_test" ··· 51 59 } 52 60 53 61 pub fn let_assert_test() { 62 + let assert 1 = 2 54 63 let assert Error(e) = 55 64 rescue(fn() { 56 65 let assert 0 = function.identity(123) ··· 73 82 let assert 0 = function.identity(321) as "oh dear" 74 83 }) 75 84 let assert Ok(e) = gleam_panic.from_dynamic(e) 85 + assert e.file == "test/gleam_panics_test.gleam" 76 86 assert e.function == "let_assert_message_test" 77 87 assert e.module == "gleam_panics_test" 78 88 assert e.line > 1 ··· 91 101 assert x 92 102 }) 93 103 let assert Ok(e) = gleam_panic.from_dynamic(e) 104 + assert e.file == "test/gleam_panics_test.gleam" 94 105 assert e.function == "assert_expression_test" 95 106 assert e.module == "gleam_panics_test" 96 107 assert e.line > 1 ··· 112 123 assert x as "maybe?" 113 124 }) 114 125 let assert Ok(e) = gleam_panic.from_dynamic(e) 126 + assert e.file == "test/gleam_panics_test.gleam" 115 127 assert e.function == "assert_expression_message_test" 116 128 assert e.module == "gleam_panics_test" 117 129 assert e.line > 1 ··· 132 144 assert function.identity(False) 133 145 }) 134 146 let assert Ok(e) = gleam_panic.from_dynamic(e) 147 + assert e.file == "test/gleam_panics_test.gleam" 135 148 assert e.function == "assert_function_test" 136 149 assert e.module == "gleam_panics_test" 137 150 assert e.line > 1 ··· 152 165 assert function.identity(False) as "oh!" 153 166 }) 154 167 let assert Ok(e) = gleam_panic.from_dynamic(e) 168 + assert e.file == "test/gleam_panics_test.gleam" 155 169 assert e.function == "assert_function_message_test" 156 170 assert e.module == "gleam_panics_test" 157 171 assert e.line > 1 ··· 173 187 assert a && function.identity(False) 174 188 }) 175 189 let assert Ok(e) = gleam_panic.from_dynamic(e) 190 + assert e.file == "test/gleam_panics_test.gleam" 176 191 assert e.function == "assert_binary_operator_test" 177 192 assert e.module == "gleam_panics_test" 178 193 assert e.line > 1