diff options
Diffstat (limited to 'aoc2023/build/packages/adglent/src/showtime/internal/common')
4 files changed, 288 insertions, 0 deletions
diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/cli.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/cli.gleam new file mode 100644 index 0000000..1c03211 --- /dev/null +++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/cli.gleam @@ -0,0 +1,5 @@ +pub type Capture { + Yes + No + Mixed +} diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/common_event_handler.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/common_event_handler.gleam new file mode 100644 index 0000000..b90af14 --- /dev/null +++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/common_event_handler.gleam @@ -0,0 +1,101 @@ +import gleam/map.{type Map} +import showtime/internal/common/test_suite.{ + type TestEvent, type TestRun, CompletedTestRun, EndTest, EndTestRun, + EndTestSuite, OngoingTestRun, StartTest, StartTestRun, StartTestSuite, +} + +pub type TestState { + NotStarted + Running + Finished(num_modules: Int) +} + +pub type HandlerState { + HandlerState( + test_state: TestState, + num_done: Int, + events: Map(String, Map(String, TestRun)), + ) +} + +// This is the common event-handler (shared between erlang/JS targets) +// The main strategy is to collect the test-results in a map of maps: +// module_name -> +// test_name -> test_result +// It will also keep track of if it is running (i.e. did it receive the EndTestRun) +// so that the caller can determine when to print test-results +pub fn handle_event( + msg: TestEvent, + system_time: fn() -> Int, + state: HandlerState, +) { + let test_state = state.test_state + let num_done = state.num_done + let events = state.events + let #(updated_test_state, updated_num_done, updated_events) = case msg { + StartTestRun -> #(Running, num_done, events) + StartTestSuite(module) -> { + let maybe_module_events = map.get(events, module.name) + let new_events = case maybe_module_events { + Ok(_) -> events + Error(_) -> + events + |> map.insert(module.name, map.new()) + } + #(test_state, num_done, new_events) + } + StartTest(module, test) -> { + let current_time = system_time() + let maybe_module_events = map.get(events, module.name) + let new_events = case maybe_module_events { + Ok(module_events) -> { + let maybe_test_event = map.get(module_events, test.name) + case maybe_test_event { + Error(_) -> + events + |> map.insert( + module.name, + module_events + |> map.insert(test.name, OngoingTestRun(test, current_time)), + ) + Ok(_) -> events + } + } + Error(_) -> events + } + #(test_state, num_done, new_events) + } + EndTest(module, test, result) -> { + let current_time = system_time() + let maybe_module_events = map.get(events, module.name) + let new_events = case maybe_module_events { + Ok(module_events) -> { + let maybe_test_run = + module_events + |> map.get(test.name) + let updated_module_events = case maybe_test_run { + Ok(OngoingTestRun(test_function, started_at)) -> + module_events + |> map.insert( + test.name, + CompletedTestRun( + test_function, + current_time - started_at, + result, + ), + ) + Error(_) -> module_events + } + events + |> map.insert(module.name, updated_module_events) + } + Error(_) -> events + } + #(test_state, num_done, new_events) + } + EndTestSuite(_) -> #(test_state, num_done + 1, events) + EndTestRun(num_modules) -> #(Finished(num_modules), num_done, events) + _ -> #(Running, num_done, events) + } + HandlerState(updated_test_state, updated_num_done, updated_events) +} diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/test_result.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_result.gleam new file mode 100644 index 0000000..a1d6bd9 --- /dev/null +++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_result.gleam @@ -0,0 +1,119 @@ +import gleam/dynamic.{type Dynamic} +import gleam/map.{type Map} + +// These are all the types used for test-results +// NOTE: These are heavily used in the erlang/js ffi:s +// so any changes here are likely to break the ffi:s unless +// the corresponding change is introduced there +// +// Futhermore this has some erlang related names that should +// probably be cleaned up, but it is used by both the ffi:s + +// Currently only one reason, but could be possible to support +// more reasons in the future +pub type IgnoreReason { + Ignore +} + +// This is the return value from running the test-function +// or ignored if the test was ignored +pub type TestReturn { + TestFunctionReturn(value: Dynamic, output_buffer: List(String)) + Ignored(reason: IgnoreReason) +} + +// All data about an exception in the test function is captured +// in this type. +// This is also where the data about the assertions will end up (in reason) +pub type Exception { + ErlangException( + class: Class, + reason: Reason, + stacktrace: TraceList, + output_buffer: List(String), + ) +} + +// Alias for the test-result which is either a TestResult (passed test, ignored or a test-definition) +// or an Exception (failed test) +pub type TestResult = + Result(TestReturn, Exception) + +// Reason is either an assert equal (which is if the error was produced by gleeunit should) +// TODO: Add other asserts +// or it is a gleam error meaning that is was produced by showtime should +// TODO: Rename GleamError to ShowtimeError +pub type Reason { + AssertEqual(details: List(ReasonDetail)) + AssertNotEqual(details: List(ReasonDetail)) + AssertMatch(details: List(ReasonDetail)) + GleamError(details: GleamErrorDetail) + GleamAssert(value: Dynamic, line_no: Int) + GenericException(value: Dynamic) +} + +// ReasonDetail is the union-type used in erlang-exceptions where the reason +// is a list of such details +pub type ReasonDetail { + Module(name: String) + ReasonLine(line_no: Int) + Expression(expression: String) + Expected(value: Dynamic) + Value(value: Dynamic) + Pattern(pattern: String) +} + +// Gleam error detail is produced by showtime should and will hold all the information +// about the assertion (both expected and got) +pub type GleamErrorDetail { + LetAssert( + module: String, + function: String, + line_no: Int, + message: String, + value: Dynamic, + ) +} + +// Class is a part of standard erlang exceptions, but also used on js-side +// TODO: Extend to include a JS specific constructor +pub type Class { + ErlangError + Exit + Throw +} + +// The trace list is part of the standard erlang exception, but is also +// emulated on js-side. +// TODO: Maybe we need a js-version that contain some js-specific trace-elements +pub type TraceList { + TraceList(traces: List(Trace)) +} + +// Trace are the elements in the trace list in an erlang exception +// TODO: Maybe add a js-specific trace (since arity is not really a js attribute) +pub type Trace { + Trace(function: String, arity: Arity, extra_info: List(ExtraInfo)) + TraceModule( + module: String, + function: String, + arity: Arity, + extra_info: List(ExtraInfo), + ) +} + +// Extra info holds information about the file and line +// as well as some dynamic data in a map +// This is currently not used in the reporter +pub type ExtraInfo { + ErrorInfo(error_info: Map(Dynamic, Dynamic)) + File(filename: String) + Line(line_no: Int) +} + +// Arity is the erlang type for arity +// Can be either a number, or a list of arguments +pub type Arity { + Num(arity: Int) + ArgList(arg_list: List(Dynamic)) +} diff --git a/aoc2023/build/packages/adglent/src/showtime/internal/common/test_suite.gleam b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_suite.gleam new file mode 100644 index 0000000..eb58d64 --- /dev/null +++ b/aoc2023/build/packages/adglent/src/showtime/internal/common/test_suite.gleam @@ -0,0 +1,63 @@ +import gleam/option.{type Option} +import showtime/internal/common/test_result.{type TestResult} +import showtime/internal/common/cli.{type Capture} + +// The state (and result) of a test function +pub type TestRun { + OngoingTestRun(test_function: TestFunction, started_at: Int) + CompletedTestRun( + test_function: TestFunction, + total_time: Int, + result: TestResult, + ) +} + +// A test module (found by discovery) +pub type TestModule { + TestModule(name: String, path: Option(String)) +} + +// A test function +pub type TestFunction { + TestFunction(name: String) +} + +// A test suite is a test module together with the test functions +// that were collected from that module +pub type TestSuite { + TestSuite(module: TestModule, tests: List(TestFunction)) +} + +// Test event for the event handler +pub type TestEvent { + StartTestRun + StartTestSuite(test_module: TestModule) + StartTest(test_module: TestModule, test_function: TestFunction) + EndTest( + test_module: TestModule, + test_function: TestFunction, + result: TestResult, + ) + EndTestSuite(test_module: TestModule) + EndTestRun(num_modules: Int) +} + +// Interface for the module handler +pub type TestModuleHandler = + fn(TestModule) -> Nil + +// Interface for the event handler +pub type TestEventHandler = + fn(TestEvent) -> Nil + +// Interface for the module collector +pub type ModuleCollector = + fn(TestModuleHandler) -> List(TestModule) + +// Interface for the function collector +pub type TestFunctionCollector = + fn(TestModule) -> TestSuite + +// Interface for the test runner +pub type TestRunner = + fn(TestSuite, TestEventHandler, List(String), Capture) -> Nil |