this repo has no description
0
fork

Configure Feed

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

Improved test output

+119 -85
+5
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## v1.4.0 - 2025-04-24 4 + 5 + - Added support for `assert`. 6 + - The console output format has been improved. 7 + 3 8 ## v1.3.1 - 2025-04-24 4 9 5 10 - Fixed printing of `let assert` crashes.
+3
gleam.toml
··· 11 11 12 12 [dependencies] 13 13 gleam_stdlib = ">= 0.60.0 and < 2.0.0" 14 + 15 + [dev-dependencies] 16 + testhelper = { "path" = "./testhelper" }
+2
manifest.toml
··· 3 3 4 4 packages = [ 5 5 { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, 6 + { name = "testhelper", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "testhelper" }, 6 7 ] 7 8 8 9 [requirements] 9 10 gleam_stdlib = { version = ">= 0.60.0 and < 2.0.0" } 11 + testhelper = { path = "./testhelper" }
+2 -6
src/gleeunit/internal/gleam_panic.gleam
··· 16 16 Panic 17 17 LetAssert( 18 18 start: Int, 19 + end: Int, 19 20 pattern_start: Int, 20 21 pattern_end: Int, 21 22 value: dynamic.Dynamic, 22 23 ) 23 - Assert( 24 - start: Int, 25 - expression_start: Int, 26 - expression_end: Int, 27 - kind: AssertKind, 28 - ) 24 + Assert(start: Int, end: Int, expression_start: Int, kind: AssertKind) 29 25 } 30 26 31 27 pub type AssertKind {
+5 -4
src/gleeunit/internal/gleam_panic_ffi.erl
··· 4 4 from_dynamic(#{ 5 5 gleam_error := assert, 6 6 start := Start, 7 - expression_start := EStart, 8 - expression_end := EEnd 7 + 'end' := End, 8 + expression_start := EStart 9 9 } = E) -> 10 - wrap(E, {assert, Start, EStart, EEnd, assert_kind(E)}); 10 + wrap(E, {assert, Start, End, EStart, assert_kind(E)}); 11 11 from_dynamic(#{ 12 12 gleam_error := let_assert, 13 13 start := Start, 14 + 'end' := End, 14 15 pattern_start := PStart, 15 16 pattern_end := PEnd, 16 17 value := Value 17 18 } = E) -> 18 - wrap(E, {let_assert, Start, PStart, PEnd, Value}); 19 + wrap(E, {let_assert, Start, End, PStart, PEnd, Value}); 19 20 from_dynamic(#{gleam_error := panic} = E) -> 20 21 wrap(E, panic); 21 22 from_dynamic(#{gleam_error := todo} = E) ->
+2 -1
src/gleeunit/internal/gleam_panic_ffi.mjs
··· 30 30 if (error.gleam_error === "let_assert") { 31 31 let kind = new LetAssert( 32 32 error.start, 33 + error.end, 33 34 error.pattern_start, 34 35 error.pattern_end, 35 36 error.value, ··· 40 41 if (error.gleam_error === "assert") { 41 42 let kind = new Assert( 42 43 error.start, 44 + error.end, 43 45 error.expression_start, 44 - error.expression_end, 45 46 assert_kind(error), 46 47 ); 47 48 return wrap(error, kind);
+35 -23
src/gleeunit/internal/reporting.gleam
··· 2 2 import gleam/dynamic 3 3 import gleam/int 4 4 import gleam/io 5 + import gleam/list 5 6 import gleam/option.{type Option} 6 7 import gleam/result 7 8 import gleam/string ··· 99 100 gleam_panic.Panic -> { 100 101 string.concat([ 101 102 bold(red("panic")) <> " " <> location <> "\n", 102 - blue(" test") <> ": " <> module <> "." <> function <> "\n", 103 - blue(" info") <> ": " <> error.message <> "\n", 103 + cyan(" test") <> ": " <> module <> "." <> function <> "\n", 104 + cyan(" info") <> ": " <> error.message <> "\n", 104 105 ]) 105 106 } 106 107 107 108 gleam_panic.Todo -> { 108 109 string.concat([ 109 110 bold(yellow("todo")) <> " " <> location <> "\n", 110 - blue(" test") <> ": " <> module <> "." <> function <> "\n", 111 - blue(" info") <> ": " <> error.message <> "\n", 111 + cyan(" test") <> ": " <> module <> "." <> function <> "\n", 112 + cyan(" info") <> ": " <> error.message <> "\n", 112 113 ]) 113 114 } 114 115 115 - gleam_panic.Assert(start:, expression_end:, kind:, ..) -> { 116 + gleam_panic.Assert(start:, end:, kind:, ..) -> { 116 117 string.concat([ 117 118 bold(red("assert")) <> " " <> location <> "\n", 118 - blue(" test") <> ": " <> module <> "." <> function <> "\n", 119 - code_snippet(src, start, expression_end), 119 + cyan(" test") <> ": " <> module <> "." <> function <> "\n", 120 + code_snippet(src, start, end), 120 121 assert_info(kind), 121 - blue(" info") <> ": " <> error.message <> "\n", 122 + cyan(" info") <> ": " <> error.message <> "\n", 122 123 ]) 123 124 } 124 125 125 126 // TODO: include the whole expression 126 - gleam_panic.LetAssert(start:, pattern_end:, value:, ..) -> { 127 + gleam_panic.LetAssert(start:, end:, value:, ..) -> { 127 128 string.concat([ 128 129 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", 130 + cyan(" test") <> ": " <> module <> "." <> function <> "\n", 131 + code_snippet(src, start, end), 132 + cyan("value") <> ": " <> string.inspect(value) <> "\n", 133 + cyan(" info") <> ": " <> error.message <> "\n", 133 134 ]) 134 135 } 135 136 } ··· 137 138 138 139 fn assert_info(kind: gleam_panic.AssertKind) -> String { 139 140 case kind { 140 - gleam_panic.BinaryOperator(operator:, left:, right:) -> 141 + gleam_panic.BinaryOperator(left:, right:, ..) -> { 141 142 string.concat([assert_value(" left", left), assert_value("right", right)]) 143 + } 142 144 143 - gleam_panic.FunctionCall(arguments:) -> todo 145 + gleam_panic.FunctionCall(arguments:) -> { 146 + arguments 147 + |> list.index_map(fn(e, i) { 148 + let number = string.pad_start(int.to_string(i), 5, " ") 149 + assert_value(number, e) 150 + }) 151 + |> string.concat 152 + } 144 153 145 - gleam_panic.OtherExpression(expression:) -> "" 154 + gleam_panic.OtherExpression(..) -> "" 146 155 } 147 156 } 148 157 149 158 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" 159 + cyan(name) <> ": " <> inspect_value(value) <> "\n" 160 + } 153 161 154 - gleam_panic.Literal(..) | gleam_panic.Unevaluated -> "" 162 + fn inspect_value(value: gleam_panic.AssertedExpression) -> String { 163 + case value.kind { 164 + gleam_panic.Unevaluated -> grey("unevaluated") 165 + gleam_panic.Literal(..) -> grey("literal") 166 + gleam_panic.Expression(value:) -> string.inspect(value) 155 167 } 156 168 } 157 169 ··· 160 172 use src <- result.try(option.to_result(src, Nil)) 161 173 use snippet <- result.try(bit_array.slice(src, start, end - start)) 162 174 use snippet <- result.try(bit_array.to_string(snippet)) 163 - let snippet = blue(" code") <> ": " <> snippet <> "\n" 175 + let snippet = cyan(" code") <> ": " <> snippet <> "\n" 164 176 Ok(snippet) 165 177 } 166 178 |> result.unwrap("") ··· 175 187 "\u{001b}[1m" <> text <> "\u{001b}[22m" 176 188 } 177 189 178 - fn blue(text: String) -> String { 179 - "\u{001b}[34m" <> text <> "\u{001b}[39m" 190 + fn cyan(text: String) -> String { 191 + "\u{001b}[36m" <> text <> "\u{001b}[39m" 180 192 } 181 193 182 194 fn yellow(text: String) -> String {
+9 -21
src/gleeunit_ffi.mjs
··· 1 1 import { readFileSync } from "fs"; 2 2 import { Ok, Error as GleamError } from "./gleam.mjs"; 3 + import * as reporting from "./gleeunit/internal/reporting.mjs"; 3 4 4 5 export function read_file(path) { 5 6 try { ··· 25 26 } 26 27 27 28 async function readRootPackageName() { 28 - let toml = await read_file("gleam.toml", "utf-8"); 29 + let toml = await async_read_file("gleam.toml", "utf-8"); 29 30 for (let line of toml.split("\n")) { 30 31 let matches = line.match(/\s*name\s*=\s*"([a-z][a-z0-9_]*)"/); // Match regexp in compiler-cli/src/new.rs in validate_name() 31 32 if (matches) return matches[1]; ··· 34 35 } 35 36 36 37 export async function main() { 37 - let passes = 0; 38 - let failures = 0; 38 + let state = reporting.new_state(); 39 39 40 40 let packageName = await readRootPackageName(); 41 41 let dist = `../${packageName}/`; ··· 47 47 if (!fnName.endsWith("_test")) continue; 48 48 try { 49 49 await module[fnName](); 50 - write(`\u001b[32m.\u001b[0m`); 51 - passes++; 50 + state = reporting.test_passed(state); 52 51 } catch (error) { 53 - let moduleName = "\n" + js_path.slice(0, -4); 54 - let line = error.line ? `:${error.line}` : ""; 55 - write(`\n❌ ${moduleName}.${fnName}${line}: ${error}\n`); 56 - failures++; 52 + let moduleName = js_path.slice(0, -4); 53 + state = reporting.test_failed(state, moduleName, fnName, error); 57 54 } 58 55 } 59 56 } 60 57 61 - console.log(` 62 - ${passes + failures} tests, ${failures} failures`); 63 - exit(failures ? 1 : 0); 58 + const status = reporting.finished(state); 59 + exit(status); 64 60 } 65 61 66 62 export function crash(message) { 67 63 throw new Error(message); 68 - } 69 - 70 - function write(message) { 71 - if (globalThis.Deno) { 72 - Deno.stdout.writeSync(new TextEncoder().encode(message)); 73 - } else { 74 - process.stdout.write(message); 75 - } 76 64 } 77 65 78 66 function exit(code) { ··· 101 89 return a + "/" + b; 102 90 } 103 91 104 - async function read_file(path) { 92 + async function async_read_file(path) { 105 93 if (globalThis.Deno) { 106 94 return Deno.readTextFile(path); 107 95 } else {
+30 -30
test/gleam_panics_test.gleam
··· 4 4 Assert, BinaryOperator, Expression, FunctionCall, LetAssert, Literal, 5 5 OtherExpression, Panic, Todo, Unevaluated, 6 6 } 7 + import testhelper 7 8 8 9 @external(erlang, "gleeunit_test_ffi", "rescue") 9 10 @external(javascript, "./gleeunit_test_ffi.mjs", "rescue") 10 11 fn rescue(f: fn() -> t) -> Result(t, dynamic.Dynamic) 11 12 12 13 pub fn panic_test() { 13 - panic 14 14 let assert Error(e) = rescue(fn() { panic }) 15 15 let assert Ok(e) = gleam_panic.from_dynamic(e) 16 16 assert e.file == "test/gleam_panics_test.gleam" ··· 22 22 } 23 23 24 24 pub fn panic_message_test() { 25 - todo 26 25 let assert Error(e) = rescue(fn() { panic as "oh my!" }) 27 26 let assert Ok(e) = gleam_panic.from_dynamic(e) 28 27 assert e.file == "test/gleam_panics_test.gleam" ··· 34 33 } 35 34 36 35 pub fn todo_test() { 37 - let assert Error(e) = rescue(fn() { todo }) 36 + let assert Error(e) = rescue(fn() { testhelper.run_todo() }) 38 37 let assert Ok(e) = gleam_panic.from_dynamic(e) 39 - assert e.file == "test/gleam_panics_test.gleam" 38 + assert e.file == "src/testhelper.gleam" 40 39 assert e.kind == Todo 41 - assert e.function == "todo_test" 42 - assert e.module == "gleam_panics_test" 40 + assert e.function == "run_todo" 41 + assert e.module == "testhelper" 43 42 assert e.line > 1 44 43 assert e.message 45 44 == "`todo` expression evaluated. This code has not yet been implemented." 46 45 } 47 46 48 47 pub fn todo_message_test() { 49 - let get_names = fn() { ["Lucy"] } 50 - assert get_names() == ["Lucy", "Nubi"] 51 - let assert Error(e) = rescue(fn() { todo as "oh my!" }) 48 + let assert Error(e) = rescue(fn() { testhelper.run_todo_message("oh my!") }) 52 49 let assert Ok(e) = gleam_panic.from_dynamic(e) 53 - assert e.file == "test/gleam_panics_test.gleam" 50 + assert e.file == "src/testhelper.gleam" 54 51 assert e.kind == Todo 55 - assert e.function == "todo_message_test" 56 - assert e.module == "gleam_panics_test" 52 + assert e.function == "run_todo_message" 53 + assert e.module == "testhelper" 57 54 assert e.line > 1 58 55 assert e.message == "oh my!" 59 56 } 60 57 61 58 pub fn let_assert_test() { 62 - let assert 1 = 2 63 59 let assert Error(e) = 64 60 rescue(fn() { 65 61 let assert 0 = function.identity(123) ··· 69 65 assert e.module == "gleam_panics_test" 70 66 assert e.line > 1 71 67 assert e.message == "Pattern match failed, no pattern matched the value." 72 - let assert LetAssert(value:, start:, pattern_start:, pattern_end:) = e.kind 68 + let assert LetAssert(value:, start:, end:, pattern_start:, pattern_end:) = 69 + e.kind 73 70 assert value == dynamic.int(123) 74 71 assert start > 1 75 72 assert pattern_start == start + 11 76 73 assert pattern_end == pattern_start + 1 74 + assert end == pattern_end + 25 77 75 } 78 76 79 77 pub fn let_assert_message_test() { ··· 87 85 assert e.module == "gleam_panics_test" 88 86 assert e.line > 1 89 87 assert e.message == "oh dear" 90 - let assert LetAssert(value:, start:, pattern_start:, pattern_end:) = e.kind 88 + let assert LetAssert(value:, start:, end:, pattern_start:, pattern_end:) = 89 + e.kind 91 90 assert value == dynamic.int(321) 92 91 assert start > 1 93 92 assert pattern_start == start + 11 94 93 assert pattern_end == pattern_start + 1 94 + assert end == pattern_end + 25 95 95 } 96 96 97 97 pub fn assert_expression_test() { ··· 106 106 assert e.module == "gleam_panics_test" 107 107 assert e.line > 1 108 108 assert e.message == "Assertion failed." 109 - let assert Assert(start:, expression_start:, expression_end:, kind:) = e.kind 109 + let assert Assert(start:, end:, expression_start:, kind:) = e.kind 110 110 assert start > 1 111 111 assert expression_start == start + 7 112 - assert expression_end == expression_start + 1 112 + assert end == expression_start + 1 113 113 let assert OtherExpression(expression:) = kind 114 114 assert expression.start == expression_start 115 - assert expression.end == expression_end 115 + assert expression.end == end 116 116 assert expression.kind == Expression(value: dynamic.bool(False)) 117 117 } 118 118 ··· 128 128 assert e.module == "gleam_panics_test" 129 129 assert e.line > 1 130 130 assert e.message == "maybe?" 131 - let assert Assert(start:, expression_start:, expression_end:, kind:) = e.kind 131 + let assert Assert(start:, end:, expression_start:, kind:) = e.kind 132 132 assert start > 1 133 133 assert expression_start == start + 7 134 - assert expression_end == expression_start + 1 134 + assert end == expression_start + 1 135 135 let assert OtherExpression(expression:) = kind 136 136 assert expression.start == expression_start 137 - assert expression.end == expression_end 137 + assert expression.end == end 138 138 assert expression.kind == Expression(value: dynamic.bool(False)) 139 139 } 140 140 ··· 149 149 assert e.module == "gleam_panics_test" 150 150 assert e.line > 1 151 151 assert e.message == "Assertion failed." 152 - let assert Assert(start:, expression_start:, expression_end:, kind:) = e.kind 152 + let assert Assert(start:, expression_start:, end:, kind:) = e.kind 153 153 assert start > 1 154 154 assert expression_start == start + 7 155 - assert expression_end == expression_start + 24 155 + assert end == expression_start + 24 156 156 let assert FunctionCall(arguments: [expression]) = kind 157 157 assert expression.start == expression_start + 18 158 - assert expression.end == expression_end - 1 158 + assert expression.end == end - 1 159 159 assert expression.kind == Literal(value: dynamic.bool(False)) 160 160 } 161 161 ··· 170 170 assert e.module == "gleam_panics_test" 171 171 assert e.line > 1 172 172 assert e.message == "oh!" 173 - let assert Assert(start:, expression_start:, expression_end:, kind:) = e.kind 173 + let assert Assert(start:, expression_start:, end:, kind:) = e.kind 174 174 assert start > 1 175 175 assert expression_start == start + 7 176 - assert expression_end == expression_start + 24 176 + assert end == expression_start + 24 177 177 let assert FunctionCall(arguments: [expression]) = kind 178 178 assert expression.start == expression_start + 18 179 - assert expression.end == expression_end - 1 179 + assert expression.end == end - 1 180 180 assert expression.kind == Literal(value: dynamic.bool(False)) 181 181 } 182 182 ··· 192 192 assert e.module == "gleam_panics_test" 193 193 assert e.line > 1 194 194 assert e.message == "Assertion failed." 195 - let assert Assert(start:, expression_start:, expression_end:, kind:) = e.kind 195 + let assert Assert(start:, expression_start:, end:, kind:) = e.kind 196 196 assert start > 1 197 197 assert expression_start == start + 7 198 - assert expression_end == expression_start + 29 198 + assert end == expression_start + 29 199 199 let assert BinaryOperator(operator:, left:, right:) = kind 200 200 assert operator == "&&" 201 201 assert left.start == expression_start ··· 203 203 assert left.kind == Expression(dynamic.bool(False)) 204 204 assert right.start == left.end + 4 205 205 assert right.end == right.start + 24 206 - assert right.end == expression_end 206 + assert right.end == end 207 207 assert right.kind == Unevaluated 208 208 }
+2
testhelper/gleam.toml
··· 1 + name = "testhelper" 2 + version = "1.0.0"
+11
testhelper/manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" }, 6 + { name = "gleeunit", version = "1.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "A7DD6C07B7DA49A6E28796058AA89E651D233B357D5607006D70619CD89DAAAB" }, 7 + ] 8 + 9 + [requirements] 10 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 11 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
+13
testhelper/src/testhelper.gleam
··· 1 + import gleam/io 2 + 3 + pub fn run_todo() -> Nil { 4 + todo 5 + } 6 + 7 + pub fn run_todo_message(message: String) -> Nil { 8 + todo as message 9 + } 10 + 11 + pub fn run_assert(value: Bool) -> Nil { 12 + assert value 13 + }