Initial commit

This commit is contained in:
Hugo Mårdbrink 2025-11-30 15:44:22 +01:00
commit a6272848f9
379 changed files with 74829 additions and 0 deletions

View file

@ -0,0 +1 @@
export * from "../prelude.mjs";

View file

@ -0,0 +1,89 @@
-module(gleeunit).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeunit.gleam").
-export([main/0]).
-export_type([atom_/0, encoding/0, report_module_name/0, gleeunit_progress_option/0, eunit_option/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type atom_() :: any().
-type encoding() :: utf8.
-type report_module_name() :: gleeunit_progress.
-type gleeunit_progress_option() :: {colored, boolean()}.
-type eunit_option() :: verbose |
no_tty |
{report, {report_module_name(), list(gleeunit_progress_option())}} |
{scale_timeouts, integer()}.
-file("src/gleeunit.gleam", 42).
-spec gleam_to_erlang_module_name(binary()) -> binary().
gleam_to_erlang_module_name(Path) ->
case gleam_stdlib:string_ends_with(Path, <<".gleam"/utf8>>) of
true ->
_pipe = Path,
_pipe@1 = gleam@string:replace(
_pipe,
<<".gleam"/utf8>>,
<<""/utf8>>
),
gleam@string:replace(_pipe@1, <<"/"/utf8>>, <<"@"/utf8>>);
false ->
_pipe@2 = Path,
_pipe@3 = gleam@string:split(_pipe@2, <<"/"/utf8>>),
_pipe@4 = gleam@list:last(_pipe@3),
_pipe@5 = gleam@result:unwrap(_pipe@4, Path),
gleam@string:replace(_pipe@5, <<".erl"/utf8>>, <<""/utf8>>)
end.
-file("src/gleeunit.gleam", 18).
-spec do_main() -> nil.
do_main() ->
Options = [verbose,
no_tty,
{report, {gleeunit_progress, [{colored, true}]}},
{scale_timeouts, 10}],
Result = begin
_pipe = gleeunit_ffi:find_files(
<<"**/*.{erl,gleam}"/utf8>>,
<<"test"/utf8>>
),
_pipe@1 = gleam@list:map(_pipe, fun gleam_to_erlang_module_name/1),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(_capture) -> erlang:binary_to_atom(_capture, utf8) end
),
gleeunit_ffi:run_eunit(_pipe@2, Options)
end,
Code = case Result of
{ok, _} ->
0;
{error, _} ->
1
end,
erlang:halt(Code).
-file("src/gleeunit.gleam", 13).
?DOC(
" Find and run all test functions for the current project using Erlang's EUnit\n"
" test framework, or a custom JavaScript test runner.\n"
"\n"
" Any Erlang or Gleam function in the `test` directory with a name ending in\n"
" `_test` is considered a test function and will be run.\n"
"\n"
" A test that panics is considered a failure.\n"
).
-spec main() -> nil.
main() ->
do_main().

View file

@ -0,0 +1,62 @@
import * as $list from "../gleam_stdlib/gleam/list.mjs";
import * as $result from "../gleam_stdlib/gleam/result.mjs";
import * as $string from "../gleam_stdlib/gleam/string.mjs";
import { CustomType as $CustomType } from "./gleam.mjs";
import { main as do_main } from "./gleeunit_ffi.mjs";
class Utf8 extends $CustomType {}
class GleeunitProgress extends $CustomType {}
class Colored extends $CustomType {
constructor($0) {
super();
this[0] = $0;
}
}
class Verbose extends $CustomType {}
class NoTty extends $CustomType {}
class Report extends $CustomType {
constructor($0) {
super();
this[0] = $0;
}
}
class ScaleTimeouts extends $CustomType {
constructor($0) {
super();
this[0] = $0;
}
}
function gleam_to_erlang_module_name(path) {
let $ = $string.ends_with(path, ".gleam");
if ($) {
let _pipe = path;
let _pipe$1 = $string.replace(_pipe, ".gleam", "");
return $string.replace(_pipe$1, "/", "@");
} else {
let _pipe = path;
let _pipe$1 = $string.split(_pipe, "/");
let _pipe$2 = $list.last(_pipe$1);
let _pipe$3 = $result.unwrap(_pipe$2, path);
return $string.replace(_pipe$3, ".erl", "");
}
}
/**
* Find and run all test functions for the current project using Erlang's EUnit
* test framework, or a custom JavaScript test runner.
*
* Any Erlang or Gleam function in the `test` directory with a name ending in
* `_test` is considered a test function and will be run.
*
* A test that panics is considered a failure.
*/
export function main() {
return do_main();
}

View file

@ -0,0 +1,180 @@
import * as $dynamic from "../../../gleam_stdlib/gleam/dynamic.mjs";
import { CustomType as $CustomType } from "../../gleam.mjs";
import { from_dynamic } from "./gleeunit_gleam_panic_ffi.mjs";
export { from_dynamic };
export class GleamPanic extends $CustomType {
constructor(message, file, module, function$, line, kind) {
super();
this.message = message;
this.file = file;
this.module = module;
this.function = function$;
this.line = line;
this.kind = kind;
}
}
export const GleamPanic$GleamPanic = (message, file, module, function$, line, kind) =>
new GleamPanic(message, file, module, function$, line, kind);
export const GleamPanic$isGleamPanic = (value) => value instanceof GleamPanic;
export const GleamPanic$GleamPanic$message = (value) => value.message;
export const GleamPanic$GleamPanic$0 = (value) => value.message;
export const GleamPanic$GleamPanic$file = (value) => value.file;
export const GleamPanic$GleamPanic$1 = (value) => value.file;
export const GleamPanic$GleamPanic$module = (value) => value.module;
export const GleamPanic$GleamPanic$2 = (value) => value.module;
export const GleamPanic$GleamPanic$function = (value) => value.function;
export const GleamPanic$GleamPanic$3 = (value) => value.function;
export const GleamPanic$GleamPanic$line = (value) => value.line;
export const GleamPanic$GleamPanic$4 = (value) => value.line;
export const GleamPanic$GleamPanic$kind = (value) => value.kind;
export const GleamPanic$GleamPanic$5 = (value) => value.kind;
export class Todo extends $CustomType {}
export const PanicKind$Todo = () => new Todo();
export const PanicKind$isTodo = (value) => value instanceof Todo;
export class Panic extends $CustomType {}
export const PanicKind$Panic = () => new Panic();
export const PanicKind$isPanic = (value) => value instanceof Panic;
export class LetAssert extends $CustomType {
constructor(start, end, pattern_start, pattern_end, value) {
super();
this.start = start;
this.end = end;
this.pattern_start = pattern_start;
this.pattern_end = pattern_end;
this.value = value;
}
}
export const PanicKind$LetAssert = (start, end, pattern_start, pattern_end, value) =>
new LetAssert(start, end, pattern_start, pattern_end, value);
export const PanicKind$isLetAssert = (value) => value instanceof LetAssert;
export const PanicKind$LetAssert$start = (value) => value.start;
export const PanicKind$LetAssert$0 = (value) => value.start;
export const PanicKind$LetAssert$end = (value) => value.end;
export const PanicKind$LetAssert$1 = (value) => value.end;
export const PanicKind$LetAssert$pattern_start = (value) => value.pattern_start;
export const PanicKind$LetAssert$2 = (value) => value.pattern_start;
export const PanicKind$LetAssert$pattern_end = (value) => value.pattern_end;
export const PanicKind$LetAssert$3 = (value) => value.pattern_end;
export const PanicKind$LetAssert$value = (value) => value.value;
export const PanicKind$LetAssert$4 = (value) => value.value;
export class Assert extends $CustomType {
constructor(start, end, expression_start, kind) {
super();
this.start = start;
this.end = end;
this.expression_start = expression_start;
this.kind = kind;
}
}
export const PanicKind$Assert = (start, end, expression_start, kind) =>
new Assert(start, end, expression_start, kind);
export const PanicKind$isAssert = (value) => value instanceof Assert;
export const PanicKind$Assert$start = (value) => value.start;
export const PanicKind$Assert$0 = (value) => value.start;
export const PanicKind$Assert$end = (value) => value.end;
export const PanicKind$Assert$1 = (value) => value.end;
export const PanicKind$Assert$expression_start = (value) =>
value.expression_start;
export const PanicKind$Assert$2 = (value) => value.expression_start;
export const PanicKind$Assert$kind = (value) => value.kind;
export const PanicKind$Assert$3 = (value) => value.kind;
export class BinaryOperator extends $CustomType {
constructor(operator, left, right) {
super();
this.operator = operator;
this.left = left;
this.right = right;
}
}
export const AssertKind$BinaryOperator = (operator, left, right) =>
new BinaryOperator(operator, left, right);
export const AssertKind$isBinaryOperator = (value) =>
value instanceof BinaryOperator;
export const AssertKind$BinaryOperator$operator = (value) => value.operator;
export const AssertKind$BinaryOperator$0 = (value) => value.operator;
export const AssertKind$BinaryOperator$left = (value) => value.left;
export const AssertKind$BinaryOperator$1 = (value) => value.left;
export const AssertKind$BinaryOperator$right = (value) => value.right;
export const AssertKind$BinaryOperator$2 = (value) => value.right;
export class FunctionCall extends $CustomType {
constructor(arguments$) {
super();
this.arguments = arguments$;
}
}
export const AssertKind$FunctionCall = (arguments$) =>
new FunctionCall(arguments$);
export const AssertKind$isFunctionCall = (value) =>
value instanceof FunctionCall;
export const AssertKind$FunctionCall$arguments = (value) => value.arguments;
export const AssertKind$FunctionCall$0 = (value) => value.arguments;
export class OtherExpression extends $CustomType {
constructor(expression) {
super();
this.expression = expression;
}
}
export const AssertKind$OtherExpression = (expression) =>
new OtherExpression(expression);
export const AssertKind$isOtherExpression = (value) =>
value instanceof OtherExpression;
export const AssertKind$OtherExpression$expression = (value) =>
value.expression;
export const AssertKind$OtherExpression$0 = (value) => value.expression;
export class AssertedExpression extends $CustomType {
constructor(start, end, kind) {
super();
this.start = start;
this.end = end;
this.kind = kind;
}
}
export const AssertedExpression$AssertedExpression = (start, end, kind) =>
new AssertedExpression(start, end, kind);
export const AssertedExpression$isAssertedExpression = (value) =>
value instanceof AssertedExpression;
export const AssertedExpression$AssertedExpression$start = (value) =>
value.start;
export const AssertedExpression$AssertedExpression$0 = (value) => value.start;
export const AssertedExpression$AssertedExpression$end = (value) => value.end;
export const AssertedExpression$AssertedExpression$1 = (value) => value.end;
export const AssertedExpression$AssertedExpression$kind = (value) => value.kind;
export const AssertedExpression$AssertedExpression$2 = (value) => value.kind;
export class Literal extends $CustomType {
constructor(value) {
super();
this.value = value;
}
}
export const ExpressionKind$Literal = (value) => new Literal(value);
export const ExpressionKind$isLiteral = (value) => value instanceof Literal;
export const ExpressionKind$Literal$value = (value) => value.value;
export const ExpressionKind$Literal$0 = (value) => value.value;
export class Expression extends $CustomType {
constructor(value) {
super();
this.value = value;
}
}
export const ExpressionKind$Expression = (value) => new Expression(value);
export const ExpressionKind$isExpression = (value) =>
value instanceof Expression;
export const ExpressionKind$Expression$value = (value) => value.value;
export const ExpressionKind$Expression$0 = (value) => value.value;
export class Unevaluated extends $CustomType {}
export const ExpressionKind$Unevaluated = () => new Unevaluated();
export const ExpressionKind$isUnevaluated = (value) =>
value instanceof Unevaluated;

View file

@ -0,0 +1,49 @@
-module(gleeunit_gleam_panic_ffi).
-export([from_dynamic/1]).
from_dynamic(#{
gleam_error := assert,
start := Start,
'end' := End,
expression_start := EStart
} = E) ->
wrap(E, {assert, Start, End, EStart, assert_kind(E)});
from_dynamic(#{
gleam_error := let_assert,
start := Start,
'end' := End,
pattern_start := PStart,
pattern_end := PEnd,
value := Value
} = E) ->
wrap(E, {let_assert, Start, End, PStart, PEnd, Value});
from_dynamic(#{gleam_error := panic} = E) ->
wrap(E, panic);
from_dynamic(#{gleam_error := todo} = E) ->
wrap(E, todo);
from_dynamic(_) ->
{error, nil}.
assert_kind(#{kind := binary_operator, left := L, right := R, operator := O}) ->
{binary_operator, atom_to_binary(O), expression(L), expression(R)};
assert_kind(#{kind := function_call, arguments := Arguments}) ->
{function_call, lists:map(fun expression/1, Arguments)};
assert_kind(#{kind := expression, expression := Expression}) ->
{other_expression, expression(Expression)}.
expression(#{start := S, 'end' := E, kind := literal, value := Value}) ->
{asserted_expression, S, E, {literal, Value}};
expression(#{start := S, 'end' := E, kind := expression, value := Value}) ->
{asserted_expression, S, E, {expression, Value}};
expression(#{start := S, 'end' := E, kind := unevaluated}) ->
{asserted_expression, S, E, unevaluated}.
wrap(#{
gleam_error := _,
file := File,
message := Message,
module := Module,
function := Function,
line := Line
}, Kind) ->
{ok, {gleam_panic, Message, File, Module, Function, Line, Kind}}.

View file

@ -0,0 +1,91 @@
import { Result$Ok, Result$Error, List$Empty, List$NonEmpty } from "../../gleam.mjs";
import {
GleamPanic$GleamPanic,
PanicKind$Todo,
PanicKind$Panic,
PanicKind$LetAssert,
PanicKind$Assert,
AssertKind$BinaryOperator,
AssertKind$FunctionCall,
AssertKind$OtherExpression,
AssertedExpression$AssertedExpression,
ExpressionKind$Literal,
ExpressionKind$Expression,
ExpressionKind$Unevaluated,
} from "./gleam_panic.mjs";
export function from_dynamic(error) {
if (!(error instanceof globalThis.Error) || !error.gleam_error) {
return Result$Error(undefined);
}
if (error.gleam_error === "todo") {
return wrap(error, PanicKind$Todo());
}
if (error.gleam_error === "panic") {
return wrap(error, PanicKind$Panic());
}
if (error.gleam_error === "let_assert") {
let kind = PanicKind$LetAssert(
error.start,
error.end,
error.pattern_start,
error.pattern_end,
error.value,
);
return wrap(error, kind);
}
if (error.gleam_error === "assert") {
let kind = PanicKind$Assert(
error.start,
error.end,
error.expression_start,
assert_kind(error),
);
return wrap(error, kind);
}
return Result$Error(undefined);
}
function assert_kind(error) {
if (error.kind == "binary_operator") {
return AssertKind$BinaryOperator(
error.operator,
expression(error.left),
expression(error.right),
);
}
if (error.kind == "function_call") {
let list = List$Empty();
let i = error.arguments.length;
while (i--) {
list = List$NonEmpty(expression(error.arguments[i]), list);
}
return AssertKind$FunctionCall(list);
}
return AssertKind$OtherExpression(expression(error.expression));
}
function expression(data) {
const expression = AssertedExpression$AssertedExpression(data.start, data.end, undefined);
if (data.kind == "literal") {
expression.kind = ExpressionKind$Literal(data.value);
} else if (data.kind == "expression") {
expression.kind = ExpressionKind$Expression(data.value);
} else {
expression.kind = ExpressionKind$Unevaluated();
}
return expression;
}
function wrap(e, kind) {
return Result$Ok(
GleamPanic$GleamPanic(e.message, e.file, e.module, e.function, e.line, kind),
);
}

View file

@ -0,0 +1,256 @@
import * as $bit_array from "../../../gleam_stdlib/gleam/bit_array.mjs";
import * as $dynamic from "../../../gleam_stdlib/gleam/dynamic.mjs";
import * as $int from "../../../gleam_stdlib/gleam/int.mjs";
import * as $io from "../../../gleam_stdlib/gleam/io.mjs";
import * as $list from "../../../gleam_stdlib/gleam/list.mjs";
import * as $option from "../../../gleam_stdlib/gleam/option.mjs";
import * as $result from "../../../gleam_stdlib/gleam/result.mjs";
import * as $string from "../../../gleam_stdlib/gleam/string.mjs";
import { Ok, Error, toList, CustomType as $CustomType } from "../../gleam.mjs";
import * as $gleam_panic from "../../gleeunit/internal/gleam_panic.mjs";
import { read_file as read_file_text } from "../../gleeunit_ffi.mjs";
export class State extends $CustomType {
constructor(passed, failed, skipped) {
super();
this.passed = passed;
this.failed = failed;
this.skipped = skipped;
}
}
export const State$State = (passed, failed, skipped) =>
new State(passed, failed, skipped);
export const State$isState = (value) => value instanceof State;
export const State$State$passed = (value) => value.passed;
export const State$State$0 = (value) => value.passed;
export const State$State$failed = (value) => value.failed;
export const State$State$1 = (value) => value.failed;
export const State$State$skipped = (value) => value.skipped;
export const State$State$2 = (value) => value.skipped;
export function new_state() {
return new State(0, 0, 0);
}
function bold(text) {
return ("\u{001b}[1m" + text) + "\u{001b}[22m";
}
function cyan(text) {
return ("\u{001b}[36m" + text) + "\u{001b}[39m";
}
function code_snippet(src, start, end) {
let _pipe = $result.try$(
$option.to_result(src, undefined),
(src) => {
return $result.try$(
$bit_array.slice(src, start, end - start),
(snippet) => {
return $result.try$(
$bit_array.to_string(snippet),
(snippet) => {
let snippet$1 = ((cyan(" code") + ": ") + snippet) + "\n";
return new Ok(snippet$1);
},
);
},
);
},
);
return $result.unwrap(_pipe, "");
}
function yellow(text) {
return ("\u{001b}[33m" + text) + "\u{001b}[39m";
}
export function test_skipped(state, module, function$) {
$io.print(((("\n" + module) + ".") + function$) + yellow(" skipped"));
return new State(state.passed, state.failed, state.skipped + 1);
}
function green(text) {
return ("\u{001b}[32m" + text) + "\u{001b}[39m";
}
export function test_passed(state) {
$io.print(green("."));
return new State(state.passed + 1, state.failed, state.skipped);
}
function red(text) {
return ("\u{001b}[31m" + text) + "\u{001b}[39m";
}
export function finished(state) {
let $ = state.failed;
if ($ === 0) {
let $1 = state.skipped;
if ($1 === 0) {
let $2 = state.passed;
if ($2 === 0) {
$io.println("\nNo tests found!");
return 1;
} else {
let message = ("\n" + $int.to_string(state.passed)) + " passed, no failures";
$io.println(green(message));
return 0;
}
} else {
let message = ((("\n" + $int.to_string(state.passed)) + " passed, 0 failures, ") + $int.to_string(
state.skipped,
)) + " skipped";
$io.println(yellow(message));
return 1;
}
} else {
let $1 = state.skipped;
if ($1 === 0) {
let message = ((("\n" + $int.to_string(state.passed)) + " passed, ") + $int.to_string(
state.failed,
)) + " failures";
$io.println(red(message));
return 1;
} else {
let message = ((((("\n" + $int.to_string(state.passed)) + " passed, ") + $int.to_string(
state.failed,
)) + " failures, ") + $int.to_string(state.skipped)) + " skipped";
$io.println(red(message));
return 1;
}
}
}
export function eunit_missing() {
let message = bold(red("Error")) + ": EUnit libraries not found.\n\nYour Erlang installation seems to be incomplete. If you installed Erlang using\na package manager ensure that you have installed the full Erlang\ndistribution instead of a stripped-down version.\n";
$io.print_error(message);
return new Error(undefined);
}
function grey(text) {
return ("\u{001b}[90m" + text) + "\u{001b}[39m";
}
function format_unknown(module, function$, error) {
return $string.concat(
toList([
grey((module + ".") + function$) + "\n",
"An unexpected error occurred:\n",
"\n",
(" " + $string.inspect(error)) + "\n",
]),
);
}
function inspect_value(value) {
let $ = value.kind;
if ($ instanceof $gleam_panic.Literal) {
return grey("literal");
} else if ($ instanceof $gleam_panic.Expression) {
let value$1 = $.value;
return $string.inspect(value$1);
} else {
return grey("unevaluated");
}
}
function assert_value(name, value) {
return ((cyan(name) + ": ") + inspect_value(value)) + "\n";
}
function assert_info(kind) {
if (kind instanceof $gleam_panic.BinaryOperator) {
let left = kind.left;
let right = kind.right;
return $string.concat(
toList([assert_value(" left", left), assert_value("right", right)]),
);
} else if (kind instanceof $gleam_panic.FunctionCall) {
let arguments$ = kind.arguments;
let _pipe = arguments$;
let _pipe$1 = $list.index_map(
_pipe,
(e, i) => {
let number = $string.pad_start($int.to_string(i), 5, " ");
return assert_value(number, e);
},
);
return $string.concat(_pipe$1);
} else {
return "";
}
}
function format_gleam_error(error, module, function$, src) {
let location = grey((error.file + ":") + $int.to_string(error.line));
let $ = error.kind;
if ($ instanceof $gleam_panic.Todo) {
return $string.concat(
toList([
((bold(yellow("todo")) + " ") + location) + "\n",
((((cyan(" test") + ": ") + module) + ".") + function$) + "\n",
((cyan(" info") + ": ") + error.message) + "\n",
]),
);
} else if ($ instanceof $gleam_panic.Panic) {
return $string.concat(
toList([
((bold(red("panic")) + " ") + location) + "\n",
((((cyan(" test") + ": ") + module) + ".") + function$) + "\n",
((cyan(" info") + ": ") + error.message) + "\n",
]),
);
} else if ($ instanceof $gleam_panic.LetAssert) {
let start = $.start;
let end = $.end;
let value = $.value;
return $string.concat(
toList([
((bold(red("let assert")) + " ") + location) + "\n",
((((cyan(" test") + ": ") + module) + ".") + function$) + "\n",
code_snippet(src, start, end),
((cyan("value") + ": ") + $string.inspect(value)) + "\n",
((cyan(" info") + ": ") + error.message) + "\n",
]),
);
} else {
let start = $.start;
let end = $.end;
let kind = $.kind;
return $string.concat(
toList([
((bold(red("assert")) + " ") + location) + "\n",
((((cyan(" test") + ": ") + module) + ".") + function$) + "\n",
code_snippet(src, start, end),
assert_info(kind),
((cyan(" info") + ": ") + error.message) + "\n",
]),
);
}
}
function read_file(path) {
let $ = read_file_text(path);
if ($ instanceof Ok) {
let text = $[0];
return new Ok($bit_array.from_string(text));
} else {
return $;
}
}
export function test_failed(state, module, function$, error) {
let _block;
let $ = $gleam_panic.from_dynamic(error);
if ($ instanceof Ok) {
let error$1 = $[0];
let src = $option.from_result(read_file(error$1.file));
_block = format_gleam_error(error$1, module, function$, src);
} else {
_block = format_unknown(module, function$, error);
}
let message = _block;
$io.print("\n" + message);
return new State(state.passed, state.failed + 1, state.skipped);
}

View file

@ -0,0 +1,135 @@
import * as $option from "../../gleam_stdlib/gleam/option.mjs";
import { None, Some } from "../../gleam_stdlib/gleam/option.mjs";
import * as $string from "../../gleam_stdlib/gleam/string.mjs";
import { Ok, Error, toList, makeError, isEqual } from "../gleam.mjs";
const FILEPATH = "src/gleeunit/should.gleam";
export function equal(a, b) {
let $ = isEqual(a, b);
if ($) {
return undefined;
} else {
throw makeError(
"panic",
FILEPATH,
"gleeunit/should",
10,
"equal",
$string.concat(
toList([
"\n",
$string.inspect(a),
"\nshould equal\n",
$string.inspect(b),
]),
),
{}
)
}
}
export function not_equal(a, b) {
let $ = !isEqual(a, b);
if ($) {
return undefined;
} else {
throw makeError(
"panic",
FILEPATH,
"gleeunit/should",
23,
"not_equal",
$string.concat(
toList([
"\n",
$string.inspect(a),
"\nshould not equal\n",
$string.inspect(b),
]),
),
{}
)
}
}
export function be_ok(a) {
if (a instanceof Ok) {
let value = a[0];
return value;
} else {
throw makeError(
"panic",
FILEPATH,
"gleeunit/should",
35,
"be_ok",
$string.concat(toList(["\n", $string.inspect(a), "\nshould be ok"])),
{}
)
}
}
export function be_error(a) {
if (a instanceof Error) {
let error = a[0];
return error;
} else {
throw makeError(
"panic",
FILEPATH,
"gleeunit/should",
42,
"be_error",
$string.concat(toList(["\n", $string.inspect(a), "\nshould be error"])),
{}
)
}
}
export function be_some(a) {
if (a instanceof Some) {
let value = a[0];
return value;
} else {
throw makeError(
"panic",
FILEPATH,
"gleeunit/should",
49,
"be_some",
$string.concat(toList(["\n", $string.inspect(a), "\nshould be some"])),
{}
)
}
}
export function be_none(a) {
if (a instanceof None) {
return undefined;
} else {
throw makeError(
"panic",
FILEPATH,
"gleeunit/should",
56,
"be_none",
$string.concat(toList(["\n", $string.inspect(a), "\nshould be none"])),
{}
)
}
}
export function be_true(actual) {
let _pipe = actual;
return equal(_pipe, true);
}
export function be_false(actual) {
let _pipe = actual;
return equal(_pipe, false);
}
export function fail() {
return be_true(false);
}

View file

@ -0,0 +1,56 @@
-module(gleeunit@internal@gleam_panic).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeunit/internal/gleam_panic.gleam").
-export([from_dynamic/1]).
-export_type([gleam_panic/0, panic_kind/0, assert_kind/0, asserted_expression/0, expression_kind/0]).
-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(false).
-type gleam_panic() :: {gleam_panic,
binary(),
binary(),
binary(),
binary(),
integer(),
panic_kind()}.
-type panic_kind() :: todo |
panic |
{let_assert,
integer(),
integer(),
integer(),
integer(),
gleam@dynamic:dynamic_()} |
{assert, integer(), integer(), integer(), assert_kind()}.
-type assert_kind() :: {binary_operator,
binary(),
asserted_expression(),
asserted_expression()} |
{function_call, list(asserted_expression())} |
{other_expression, asserted_expression()}.
-type asserted_expression() :: {asserted_expression,
integer(),
integer(),
expression_kind()}.
-type expression_kind() :: {literal, gleam@dynamic:dynamic_()} |
{expression, gleam@dynamic:dynamic_()} |
unevaluated.
-file("src/gleeunit/internal/gleam_panic.gleam", 49).
?DOC(false).
-spec from_dynamic(gleam@dynamic:dynamic_()) -> {ok, gleam_panic()} |
{error, nil}.
from_dynamic(Data) ->
gleeunit_gleam_panic_ffi:from_dynamic(Data).

View file

@ -0,0 +1,343 @@
-module(gleeunit@internal@reporting).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeunit/internal/reporting.gleam").
-export([new_state/0, test_skipped/3, test_passed/1, finished/1, eunit_missing/0, test_failed/4]).
-export_type([state/0]).
-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(false).
-type state() :: {state, integer(), integer(), integer()}.
-file("src/gleeunit/internal/reporting.gleam", 15).
?DOC(false).
-spec new_state() -> state().
new_state() ->
{state, 0, 0, 0}.
-file("src/gleeunit/internal/reporting.gleam", 207).
?DOC(false).
-spec bold(binary()) -> binary().
bold(Text) ->
<<<<"\x{001b}[1m"/utf8, Text/binary>>/binary, "\x{001b}[22m"/utf8>>.
-file("src/gleeunit/internal/reporting.gleam", 211).
?DOC(false).
-spec cyan(binary()) -> binary().
cyan(Text) ->
<<<<"\x{001b}[36m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>.
-file("src/gleeunit/internal/reporting.gleam", 191).
?DOC(false).
-spec code_snippet(gleam@option:option(bitstring()), integer(), integer()) -> binary().
code_snippet(Src, Start, End) ->
_pipe = begin
gleam@result:'try'(
gleam@option:to_result(Src, nil),
fun(Src@1) ->
gleam@result:'try'(
gleam_stdlib:bit_array_slice(Src@1, Start, End - Start),
fun(Snippet) ->
gleam@result:'try'(
gleam@bit_array:to_string(Snippet),
fun(Snippet@1) ->
Snippet@2 = <<<<<<(cyan(<<" code"/utf8>>))/binary,
": "/utf8>>/binary,
Snippet@1/binary>>/binary,
"\n"/utf8>>,
{ok, Snippet@2}
end
)
end
)
end
)
end,
gleam@result:unwrap(_pipe, <<""/utf8>>).
-file("src/gleeunit/internal/reporting.gleam", 215).
?DOC(false).
-spec yellow(binary()) -> binary().
yellow(Text) ->
<<<<"\x{001b}[33m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>.
-file("src/gleeunit/internal/reporting.gleam", 202).
?DOC(false).
-spec test_skipped(state(), binary(), binary()) -> state().
test_skipped(State, Module, Function) ->
gleam_stdlib:print(
<<<<<<<<"\n"/utf8, Module/binary>>/binary, "."/utf8>>/binary,
Function/binary>>/binary,
(yellow(<<" skipped"/utf8>>))/binary>>
),
{state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State) + 1}.
-file("src/gleeunit/internal/reporting.gleam", 219).
?DOC(false).
-spec green(binary()) -> binary().
green(Text) ->
<<<<"\x{001b}[32m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>.
-file("src/gleeunit/internal/reporting.gleam", 66).
?DOC(false).
-spec test_passed(state()) -> state().
test_passed(State) ->
gleam_stdlib:print(green(<<"."/utf8>>)),
{state,
erlang:element(2, State) + 1,
erlang:element(3, State),
erlang:element(4, State)}.
-file("src/gleeunit/internal/reporting.gleam", 223).
?DOC(false).
-spec red(binary()) -> binary().
red(Text) ->
<<<<"\x{001b}[31m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>.
-file("src/gleeunit/internal/reporting.gleam", 19).
?DOC(false).
-spec finished(state()) -> integer().
finished(State) ->
case State of
{state, 0, 0, 0} ->
gleam_stdlib:println(<<"\nNo tests found!"/utf8>>),
1;
{state, _, 0, 0} ->
Message = <<<<"\n"/utf8,
(erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary,
" passed, no failures"/utf8>>,
gleam_stdlib:println(green(Message)),
0;
{state, _, _, 0} ->
Message@1 = <<<<<<<<"\n"/utf8,
(erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary,
" passed, "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(3, State)))/binary>>/binary,
" failures"/utf8>>,
gleam_stdlib:println(red(Message@1)),
1;
{state, _, 0, _} ->
Message@2 = <<<<<<<<"\n"/utf8,
(erlang:integer_to_binary(erlang:element(2, State)))/binary>>/binary,
" passed, 0 failures, "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(4, State)))/binary>>/binary,
" skipped"/utf8>>,
gleam_stdlib:println(yellow(Message@2)),
1;
{state, _, _, _} ->
Message@3 = <<<<<<<<<<<<"\n"/utf8,
(erlang:integer_to_binary(
erlang:element(2, State)
))/binary>>/binary,
" passed, "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(3, State)))/binary>>/binary,
" failures, "/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(4, State)))/binary>>/binary,
" skipped"/utf8>>,
gleam_stdlib:println(red(Message@3)),
1
end.
-file("src/gleeunit/internal/reporting.gleam", 89).
?DOC(false).
-spec eunit_missing() -> {ok, any()} | {error, nil}.
eunit_missing() ->
Message = <<(bold(red(<<"Error"/utf8>>)))/binary,
": EUnit libraries not found.
Your Erlang installation seems to be incomplete. If you installed Erlang using
a package manager ensure that you have installed the full Erlang
distribution instead of a stripped-down version.
"/utf8>>,
gleam_stdlib:print_error(Message),
{error, nil}.
-file("src/gleeunit/internal/reporting.gleam", 227).
?DOC(false).
-spec grey(binary()) -> binary().
grey(Text) ->
<<<<"\x{001b}[90m"/utf8, Text/binary>>/binary, "\x{001b}[39m"/utf8>>.
-file("src/gleeunit/internal/reporting.gleam", 100).
?DOC(false).
-spec format_unknown(binary(), binary(), gleam@dynamic:dynamic_()) -> binary().
format_unknown(Module, Function, Error) ->
erlang:list_to_binary(
[<<(grey(<<<<Module/binary, "."/utf8>>/binary, Function/binary>>))/binary,
"\n"/utf8>>,
<<"An unexpected error occurred:\n"/utf8>>,
<<"\n"/utf8>>,
<<<<" "/utf8, (gleam@string:inspect(Error))/binary>>/binary,
"\n"/utf8>>]
).
-file("src/gleeunit/internal/reporting.gleam", 183).
?DOC(false).
-spec inspect_value(gleeunit@internal@gleam_panic:asserted_expression()) -> binary().
inspect_value(Value) ->
case erlang:element(4, Value) of
unevaluated ->
grey(<<"unevaluated"/utf8>>);
{literal, _} ->
grey(<<"literal"/utf8>>);
{expression, Value@1} ->
gleam@string:inspect(Value@1)
end.
-file("src/gleeunit/internal/reporting.gleam", 179).
?DOC(false).
-spec assert_value(
binary(),
gleeunit@internal@gleam_panic:asserted_expression()
) -> binary().
assert_value(Name, Value) ->
<<<<<<(cyan(Name))/binary, ": "/utf8>>/binary,
(inspect_value(Value))/binary>>/binary,
"\n"/utf8>>.
-file("src/gleeunit/internal/reporting.gleam", 160).
?DOC(false).
-spec assert_info(gleeunit@internal@gleam_panic:assert_kind()) -> binary().
assert_info(Kind) ->
case Kind of
{binary_operator, _, Left, Right} ->
erlang:list_to_binary(
[assert_value(<<" left"/utf8>>, Left),
assert_value(<<"right"/utf8>>, Right)]
);
{function_call, Arguments} ->
_pipe = Arguments,
_pipe@1 = gleam@list:index_map(
_pipe,
fun(E, I) ->
Number = gleam@string:pad_start(
erlang:integer_to_binary(I),
5,
<<" "/utf8>>
),
assert_value(Number, E)
end
),
erlang:list_to_binary(_pipe@1);
{other_expression, _} ->
<<""/utf8>>
end.
-file("src/gleeunit/internal/reporting.gleam", 113).
?DOC(false).
-spec format_gleam_error(
gleeunit@internal@gleam_panic:gleam_panic(),
binary(),
binary(),
gleam@option:option(bitstring())
) -> binary().
format_gleam_error(Error, Module, Function, Src) ->
Location = grey(
<<<<(erlang:element(3, Error))/binary, ":"/utf8>>/binary,
(erlang:integer_to_binary(erlang:element(6, Error)))/binary>>
),
case erlang:element(7, Error) of
panic ->
erlang:list_to_binary(
[<<<<<<(bold(red(<<"panic"/utf8>>)))/binary, " "/utf8>>/binary,
Location/binary>>/binary,
"\n"/utf8>>,
<<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary,
Module/binary>>/binary,
"."/utf8>>/binary,
Function/binary>>/binary,
"\n"/utf8>>,
<<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary,
(erlang:element(2, Error))/binary>>/binary,
"\n"/utf8>>]
);
todo ->
erlang:list_to_binary(
[<<<<<<(bold(yellow(<<"todo"/utf8>>)))/binary, " "/utf8>>/binary,
Location/binary>>/binary,
"\n"/utf8>>,
<<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary,
Module/binary>>/binary,
"."/utf8>>/binary,
Function/binary>>/binary,
"\n"/utf8>>,
<<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary,
(erlang:element(2, Error))/binary>>/binary,
"\n"/utf8>>]
);
{assert, Start, End, _, Kind} ->
erlang:list_to_binary(
[<<<<<<(bold(red(<<"assert"/utf8>>)))/binary, " "/utf8>>/binary,
Location/binary>>/binary,
"\n"/utf8>>,
<<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary,
Module/binary>>/binary,
"."/utf8>>/binary,
Function/binary>>/binary,
"\n"/utf8>>,
code_snippet(Src, Start, End),
assert_info(Kind),
<<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary,
(erlang:element(2, Error))/binary>>/binary,
"\n"/utf8>>]
);
{let_assert, Start@1, End@1, _, _, Value} ->
erlang:list_to_binary(
[<<<<<<(bold(red(<<"let assert"/utf8>>)))/binary, " "/utf8>>/binary,
Location/binary>>/binary,
"\n"/utf8>>,
<<<<<<<<<<(cyan(<<" test"/utf8>>))/binary, ": "/utf8>>/binary,
Module/binary>>/binary,
"."/utf8>>/binary,
Function/binary>>/binary,
"\n"/utf8>>,
code_snippet(Src, Start@1, End@1),
<<<<<<(cyan(<<"value"/utf8>>))/binary, ": "/utf8>>/binary,
(gleam@string:inspect(Value))/binary>>/binary,
"\n"/utf8>>,
<<<<<<(cyan(<<" info"/utf8>>))/binary, ": "/utf8>>/binary,
(erlang:element(2, Error))/binary>>/binary,
"\n"/utf8>>]
)
end.
-file("src/gleeunit/internal/reporting.gleam", 71).
?DOC(false).
-spec test_failed(state(), binary(), binary(), gleam@dynamic:dynamic_()) -> state().
test_failed(State, Module, Function, Error) ->
Message = case gleeunit_gleam_panic_ffi:from_dynamic(Error) of
{ok, Error@1} ->
Src = gleam@option:from_result(
file:read_file(erlang:element(3, Error@1))
),
format_gleam_error(Error@1, Module, Function, Src);
{error, _} ->
format_unknown(Module, Function, Error)
end,
gleam_stdlib:print(<<"\n"/utf8, Message/binary>>),
{state,
erlang:element(2, State),
erlang:element(3, State) + 1,
erlang:element(4, State)}.

View file

@ -0,0 +1,153 @@
-module(gleeunit@should).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeunit/should.gleam").
-export([equal/2, not_equal/2, be_ok/1, be_error/1, be_some/1, be_none/1, be_true/1, be_false/1, fail/0]).
-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(" Use the `assert` keyword instead of this module.\n").
-file("src/gleeunit/should.gleam", 6).
-spec equal(DOF, DOF) -> nil.
equal(A, B) ->
case A =:= B of
true ->
nil;
_ ->
erlang:error(#{gleam_error => panic,
message => erlang:list_to_binary(
[<<"\n"/utf8>>,
gleam@string:inspect(A),
<<"\nshould equal\n"/utf8>>,
gleam@string:inspect(B)]
),
file => <<?FILEPATH/utf8>>,
module => <<"gleeunit/should"/utf8>>,
function => <<"equal"/utf8>>,
line => 10})
end.
-file("src/gleeunit/should.gleam", 19).
-spec not_equal(DOG, DOG) -> nil.
not_equal(A, B) ->
case A /= B of
true ->
nil;
_ ->
erlang:error(#{gleam_error => panic,
message => erlang:list_to_binary(
[<<"\n"/utf8>>,
gleam@string:inspect(A),
<<"\nshould not equal\n"/utf8>>,
gleam@string:inspect(B)]
),
file => <<?FILEPATH/utf8>>,
module => <<"gleeunit/should"/utf8>>,
function => <<"not_equal"/utf8>>,
line => 23})
end.
-file("src/gleeunit/should.gleam", 32).
-spec be_ok({ok, DOH} | {error, any()}) -> DOH.
be_ok(A) ->
case A of
{ok, Value} ->
Value;
_ ->
erlang:error(#{gleam_error => panic,
message => erlang:list_to_binary(
[<<"\n"/utf8>>,
gleam@string:inspect(A),
<<"\nshould be ok"/utf8>>]
),
file => <<?FILEPATH/utf8>>,
module => <<"gleeunit/should"/utf8>>,
function => <<"be_ok"/utf8>>,
line => 35})
end.
-file("src/gleeunit/should.gleam", 39).
-spec be_error({ok, any()} | {error, DOM}) -> DOM.
be_error(A) ->
case A of
{error, Error} ->
Error;
_ ->
erlang:error(#{gleam_error => panic,
message => erlang:list_to_binary(
[<<"\n"/utf8>>,
gleam@string:inspect(A),
<<"\nshould be error"/utf8>>]
),
file => <<?FILEPATH/utf8>>,
module => <<"gleeunit/should"/utf8>>,
function => <<"be_error"/utf8>>,
line => 42})
end.
-file("src/gleeunit/should.gleam", 46).
-spec be_some(gleam@option:option(DOP)) -> DOP.
be_some(A) ->
case A of
{some, Value} ->
Value;
_ ->
erlang:error(#{gleam_error => panic,
message => erlang:list_to_binary(
[<<"\n"/utf8>>,
gleam@string:inspect(A),
<<"\nshould be some"/utf8>>]
),
file => <<?FILEPATH/utf8>>,
module => <<"gleeunit/should"/utf8>>,
function => <<"be_some"/utf8>>,
line => 49})
end.
-file("src/gleeunit/should.gleam", 53).
-spec be_none(gleam@option:option(any())) -> nil.
be_none(A) ->
case A of
none ->
nil;
_ ->
erlang:error(#{gleam_error => panic,
message => erlang:list_to_binary(
[<<"\n"/utf8>>,
gleam@string:inspect(A),
<<"\nshould be none"/utf8>>]
),
file => <<?FILEPATH/utf8>>,
module => <<"gleeunit/should"/utf8>>,
function => <<"be_none"/utf8>>,
line => 56})
end.
-file("src/gleeunit/should.gleam", 60).
-spec be_true(boolean()) -> nil.
be_true(Actual) ->
_pipe = Actual,
equal(_pipe, true).
-file("src/gleeunit/should.gleam", 65).
-spec be_false(boolean()) -> nil.
be_false(Actual) ->
_pipe = Actual,
equal(_pipe, false).
-file("src/gleeunit/should.gleam", 70).
-spec fail() -> nil.
fail() ->
be_true(false).

View file

@ -0,0 +1,21 @@
-module(gleeunit_ffi).
-export([find_files/2, run_eunit/2]).
find_files(Pattern, In) ->
Results = filelib:wildcard(binary_to_list(Pattern), binary_to_list(In)),
lists:map(fun list_to_binary/1, Results).
run_eunit(Tests, Options) ->
case code:which(eunit) of
non_existing ->
gleeunit@internal@reporting:eunit_missing();
_ ->
case eunit:test(Tests, Options) of
ok -> {ok, nil};
error -> {error, nil};
{error, Term} -> {error, Term}
end
end.

View file

@ -0,0 +1,100 @@
import { readFileSync } from "node:fs";
import { Result$Ok, Result$Error } from "./gleam.mjs";
import * as reporting from "./gleeunit/internal/reporting.mjs";
export function read_file(path) {
try {
return Result$Ok(readFileSync(path));
} catch {
return Result$Error(undefined);
}
}
async function* gleamFiles(directory) {
for (let entry of await read_dir(directory)) {
let path = join_path(directory, entry);
if (path.endsWith(".gleam")) {
yield path;
} else {
try {
yield* gleamFiles(path);
} catch (error) {
// Could not read directory, assume it's a file
}
}
}
}
async function readRootPackageName() {
let toml = await async_read_file("gleam.toml", "utf-8");
for (let line of toml.split("\n")) {
let matches = line.match(/\s*name\s*=\s*"([a-z][a-z0-9_]*)"/); // Match regexp in compiler-cli/src/new.rs in validate_name()
if (matches) return matches[1];
}
throw new Error("Could not determine package name from gleam.toml");
}
export async function main() {
let state = reporting.new_state();
let packageName = await readRootPackageName();
let dist = `../${packageName}/`;
for await (let path of await gleamFiles("test")) {
let js_path = path.slice("test/".length).replace(".gleam", ".mjs");
let module = await import(join_path(dist, js_path));
for (let fnName of Object.keys(module)) {
if (!fnName.endsWith("_test")) continue;
try {
await module[fnName]();
state = reporting.test_passed(state);
} catch (error) {
let moduleName = js_path.slice(0, -4);
state = reporting.test_failed(state, moduleName, fnName, error);
}
}
}
const status = reporting.finished(state);
exit(status);
}
export function crash(message) {
throw new Error(message);
}
function exit(code) {
if (globalThis.Deno) {
Deno.exit(code);
} else {
process.exit(code);
}
}
async function read_dir(path) {
if (globalThis.Deno) {
let items = [];
for await (let item of Deno.readDir(path, { withFileTypes: true })) {
items.push(item.name);
}
return items;
} else {
let { readdir } = await import("node:fs/promises");
return readdir(path);
}
}
function join_path(a, b) {
if (a.endsWith("/")) return a + b;
return a + "/" + b;
}
async function async_read_file(path) {
if (globalThis.Deno) {
return Deno.readTextFile(path);
} else {
let { readFile } = await import("node:fs/promises");
let contents = await readFile(path);
return contents.toString();
}
}

View file

@ -0,0 +1,72 @@
%% A formatter adapted from Sean Cribb's https://github.com/seancribbs/eunit_formatters
-module(gleeunit_progress).
-define(NOTEST, true).
%% eunit_listener callbacks
-export([
init/1, handle_begin/3, handle_end/3, handle_cancel/3, terminate/2,
start/0, start/1
]).
-define(reporting, gleeunit@internal@reporting).
start() ->
start([]).
start(Options) ->
eunit_listener:start(?MODULE, Options).
init(_Options) ->
?reporting:new_state().
handle_begin(_test_or_group, _data, State) ->
State.
handle_end(group, _data, State) ->
State;
handle_end(test, Data, State) ->
{AtomModule, AtomFunction, _Arity} = proplists:get_value(source, Data),
Module = erlang:atom_to_binary(AtomModule),
Function = erlang:atom_to_binary(AtomFunction),
% EUnit swallows stdout, so print it to make debugging easier.
case proplists:get_value(output, Data) of
undefined -> ok;
<<>> -> ok;
Out -> gleam@io:print(Out)
end,
case proplists:get_value(status, Data) of
ok ->
?reporting:test_passed(State);
{skipped, _Reason} ->
?reporting:test_skipped(State, Module, Function);
{error, {_, Exception, _Stack}} ->
?reporting:test_failed(State, Module, Function, Exception)
end.
handle_cancel(_test_or_group, Data, State) ->
?reporting:test_failed(State, <<"gleeunit">>, <<"main">>, Data).
terminate({ok, _Data}, State) ->
?reporting:finished(State),
ok;
terminate({error, Reason}, State) ->
?reporting:finished(State),
io:fwrite("
Eunit failed:
~80p
This is probably a bug in gleeunit. Please report it.
", [Reason]),
sync_end(error).
sync_end(Result) ->
receive
{stop, Reference, ReplyTo} ->
ReplyTo ! {result, Reference, Result},
ok
end.