Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: "25.1"
gleam-version: "1.2.0"
otp-version: "28"
gleam-version: "1.11.0-rc2"
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v1.4.0 - 2025-04-24

- Added support for `assert`.
- The console output format has been improved.
- The `gleeunit/should` module has been soft-deprecated in favour of Gleam's
`assert`. In future releases this module will emit a warning when used.

## v1.3.1 - 2025-04-24

- Fixed printing of `let assert` crashes.
Expand Down
7 changes: 5 additions & 2 deletions gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ licences = ["Apache-2.0"]
description = "Gleam bindings to Erlang's EUnit test framework"
repository = { type = "github", user = "lpil", repo = "gleeunit" }
links = [{ title = "Sponsor", href = "https://github.com/sponsors/lpil" }]
gleam = ">= 0.33.0"
gleam = ">= 1.11.0"

[javascript.deno]
allow_read = ["gleam.toml", "test", "build"]

[dependencies]
gleam_stdlib = ">= 0.33.0 and < 2.0.0"
gleam_stdlib = ">= 0.60.0 and < 1.0.0"

[dev-dependencies]
testhelper = { "path" = "./testhelper" }
6 changes: 4 additions & 2 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
# You typically do not need to edit this file

packages = [
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
{ name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" },
{ name = "testhelper", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "testhelper" },
]

[requirements]
gleam_stdlib = { version = ">= 0.33.0 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.60.0 and < 1.0.0" }
testhelper = { path = "./testhelper" }
49 changes: 49 additions & 0 deletions src/gleeunit/internal/gleam_panic.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import gleam/dynamic

pub type GleamPanic {
GleamPanic(
message: String,
file: String,
module: String,
function: String,
line: Int,
kind: PanicKind,
)
}

pub type PanicKind {
Todo
Panic
LetAssert(
start: Int,
end: Int,
pattern_start: Int,
pattern_end: Int,
value: dynamic.Dynamic,
)
Assert(start: Int, end: Int, expression_start: Int, kind: AssertKind)
}

pub type AssertKind {
BinaryOperator(
operator: String,
left: AssertedExpression,
right: AssertedExpression,
)
FunctionCall(arguments: List(AssertedExpression))
OtherExpression(expression: AssertedExpression)
}

pub type AssertedExpression {
AssertedExpression(start: Int, end: Int, kind: ExpressionKind)
}

pub type ExpressionKind {
Literal(value: dynamic.Dynamic)
Expression(value: dynamic.Dynamic)
Unevaluated
}

@external(erlang, "gleeunit_gleam_panic_ffi", "from_dynamic")
@external(javascript, "./gleeunit_gleam_panic_ffi.mjs", "from_dynamic")
pub fn from_dynamic(data: dynamic.Dynamic) -> Result(GleamPanic, Nil)
49 changes: 49 additions & 0 deletions src/gleeunit/internal/gleeunit_gleam_panic_ffi.erl
Original file line number Diff line number Diff line change
@@ -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}}.
91 changes: 91 additions & 0 deletions src/gleeunit/internal/gleeunit_gleam_panic_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Ok, Error, Empty, NonEmpty } from "../../gleam.mjs";
import {
GleamPanic,
Todo,
Panic,
LetAssert,
Assert,
BinaryOperator,
FunctionCall,
OtherExpression,
AssertedExpression,
Literal,
Expression,
Unevaluated,
} from "./gleam_panic.mjs";

export function from_dynamic(error) {
if (!(error instanceof globalThis.Error) || !error.gleam_error) {
return new Error(undefined);
}

if (error.gleam_error === "todo") {
return wrap(error, new Todo());
}

if (error.gleam_error === "panic") {
return wrap(error, new Panic());
}

if (error.gleam_error === "let_assert") {
let kind = new LetAssert(
error.start,
error.end,
error.pattern_start,
error.pattern_end,
error.value,
);
return wrap(error, kind);
}

if (error.gleam_error === "assert") {
let kind = new Assert(
error.start,
error.end,
error.expression_start,
assert_kind(error),
);
return wrap(error, kind);
}

return new Error(undefined);
}

function assert_kind(error) {
if (error.kind == "binary_operator") {
return new BinaryOperator(
error.operator,
expression(error.left),
expression(error.right),
);
}

if (error.kind == "function_call") {
let list = new Empty();
let i = error.arguments.length;
while (i--) {
list = new NonEmpty(expression(error.arguments[i]), list);
}
return new FunctionCall(list);
}

return new OtherExpression(expression(error.expression));
}

function expression(data) {
const expression = new AssertedExpression(data.start, data.end, undefined);
if (data.kind == "literal") {
expression.kind = new Literal(data.value);
} else if (data.kind == "expression") {
expression.kind = new Expression(data.value);
} else {
expression.kind = new Unevaluated();
}
return expression;
}

function wrap(e, kind) {
return new Ok(
new GleamPanic(e.message, e.file, e.module, e.function, e.line, kind),
);
}
Loading