diff --git a/CLAUDE.md b/CLAUDE.md index 3cab778..4dd92ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,6 @@ just setup # Install .NET tools (Fable, Paket, Fantomas, ShipIt) just restore # Restore NuGet dependencies just build # Build F# source just test # Full pipeline: F# → Erlang → compile → run on BEAM -just test-dotnet # Verify F# compiles (no BEAM needed) just format # Format with Fantomas just format-check # Check formatting just dev=true test # Test against local ../fable repo instead of dotnet tool @@ -26,29 +25,53 @@ just dev=true test # Test against local ../fable repo instead of dotnet tool ### Test pipeline detail (`just test`) 1. `dotnet build test/` — compile F# to IL -2. `dotnet fable test/ --lang Erlang --outDir build/tests` — transpile to `.erl` -3. Copy `test/test_runner.erl` into `build/tests/src/` +2. `dotnet fable test/ --lang Erlang --outDir build/tests` — transpile to `.erl` (Quill's + `[]` in `Main.fs` becomes `main:main/1`) +3. Copy the helper servers (`test_counter_server.erl`, `test_basic_sup.erl`) + `rebar.config` + into `build/tests/src/` 4. `cd build/tests && rebar3 compile` — compile Erlang to BEAM bytecode -5. `erl -noshell ...` — run test_runner which discovers and executes all `test_*` functions +5. `erl -noshell ...` — run `main:main([])`, the Scriptorium (Quill) runner, which executes the + registered suites and halts the VM with its exit code (non-zero on failure) + +The test project consumes Scriptorium from NuGet (`Scriptorium.Quill` + `Scriptorium.Nib`) via +explicit `PackageReference`s, pinned to the same versions as `../Fable.Actor`. Fable.Core is also +pinned explicitly — an unpinned, paket-injected Fable.Core left `Compiler.isDotnet` undefined when +Fable transpiled Scriptorium's shipped source and failed the BEAM build. ## Writing Tests -Tests live in `test/Test*.fs`. Each test function is marked `[]` and uses `equal` for assertions: +Tests live in `test/Test*.fs` and use the Scriptorium test framework: **Nib** for assertions and +the **Quill** runner to execute them. Each file exposes a `tests` value and registers itself in +`Main.fs`: ```fsharp -open Fable.Beam.Testing - -[] -let ``test something works`` () = - let result = 2 + 2 - result |> equal 4 +module Fable.Beam.Tests.Foo + +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test + +let tests = + testList ( + "Foo", + [ test ("something works", fun _ -> + let result = 2 + 2 + assertThat result (isEqualTo 4) ) ] + ) ``` -The test runner discovers functions prefixed with `test_` in modules prefixed with `test_`. -F# test names like `` ``test something works`` `` compile to `test_something_works` in Erlang. +- `test ("desc", fun _ -> ...)` registers one test; `testList` groups them. +- `assertThat actual (expected)` is the Nib assertion; chain with `>>` (e.g. `isGreaterThan 0 >> isEven`). +- No `#if FABLE_COMPILER` needed — Scriptorium runs directly on the BEAM (Fable.Beam's target platform), so write each test body once. +- Quill halts the VM with a non-zero exit code on failure, so a failing test fails `just test`. + +To add a new test file: create `test/TestFoo.fs`, expose `let tests = ...`, then add +`` to `test/Fable.Beam.Test.fsproj` (order matters — put before +`Main.fs`) and register `Foo.tests` in `Main.fs`. -To add a new test file: create `test/TestFoo.fs` and add `` -to `test/Fable.Beam.Test.fsproj` (order matters — put before `Main.fs`). +> **Migration status:** the suite is migrating from the old `[]` + Erlang `test_runner.erl` +> discovery to Scriptorium. Only the files that expose a `tests` value are compiled (see the fsproj); +> re-add each remaining file as it migrates. ## Writing Bindings @@ -70,7 +93,7 @@ Key rules: src/ otp/ — Bindings for OTP stdlib modules (Erlang.fs, GenServer.fs, Ets.fs, ...) cowboy/ — Bindings for Cowboy HTTP framework (separate NuGet package) -test/ — Test files (Test*.fs) + test_runner.erl +test/ — Test files (Test*.fs) using Scriptorium; helper .erl servers for gen_server/supervisor tests build/tests/ — Generated: transpiled .erl files, rebar3 project, compiled BEAM ``` diff --git a/README.md b/README.md index 6877666..10ff072 100644 --- a/README.md +++ b/README.md @@ -219,9 +219,6 @@ just # Build and run tests on BEAM just test -# Verify F# compiles (without BEAM) -just test-dotnet - # Format code just format diff --git a/justfile b/justfile index b5a6ddf..482ed50 100644 --- a/justfile +++ b/justfile @@ -33,40 +33,32 @@ clean-all: clean build: dotnet build {{src_path}} -# Transpile tests to Erlang and compile with rebar3 +# Transpile tests to Erlang and compile with rebar3. The entry point is Quill (Main.fs), which +# Fable emits as main:main/1 -- there is no test_runner.erl anymore. build-beam: dotnet build {{test_path}} {{fable}} {{test_path}} --lang Erlang --outDir {{build_path}}/tests - cp {{test_path}}/test_runner.erl {{test_path}}/test_counter_server.erl {{test_path}}/test_basic_sup.erl {{build_path}}/tests/src/ + cp {{test_path}}/test_counter_server.erl {{test_path}}/test_basic_sup.erl {{build_path}}/tests/src/ cp {{test_path}}/rebar.config {{build_path}}/tests/rebar.config cd {{build_path}}/tests && rebar3 compile -# Run BEAM tests (transpile F# to Erlang, compile, run on BEAM) +# Run BEAM tests via the Scriptorium (Quill) runner. main:main/1 runs the registered suites and +# halts the VM with its exit code, so a failing test fails this recipe. The scriptorium_* ebins are +# needed on the code path -- Quill's DSL lives in scriptorium_quill_dsl etc., which erl will not +# load unless their ebin dirs are on the path (an unloaded module shows up as an undef call). test: build-beam @echo "" cd {{build_path}}/tests && erl -noshell \ -pa _build/default/lib/fable_beam_test/ebin \ -pa _build/default/lib/fable_library_beam/ebin \ -pa _build/default/lib/jsx/ebin \ - -eval 'test_runner:main(["_build/default/lib/fable_beam_test/ebin"])' \ + -pa _build/default/lib/scriptorium_quill/ebin \ + -pa _build/default/lib/scriptorium_nib/ebin \ + -pa _build/default/lib/scriptorium_parchment/ebin \ + -pa _build/default/lib/scriptorium_ink/ebin \ + -eval 'main:main([])' \ -s init stop -# Run only the dotnet build (verify F# compiles) -test-dotnet: - dotnet build {{test_path}} - dotnet run --project {{test_path}} - -# Spike: run the Scriptorium test framework (Nib + Quill) on the BEAM. -# No test_runner.erl and no []: Quill's runner is the [], which Fable emits as -# main:main/1. It halts the VM with the suite's exit code, so a failing test fails this recipe. -spike: - dotnet build spike/scriptorium - {{fable}} spike/scriptorium --lang beam -o spike/scriptorium/beam-build - cd spike/scriptorium/beam-build && rebar3 compile - @echo "" - cd spike/scriptorium/beam-build && \ - ERL_LIBS="$(pwd)/_build/default/lib" erl -noshell -eval 'main:main([])' -s init stop - # Create NuGet packages with versions from changelogs pack: #!/usr/bin/env bash diff --git a/spike/scriptorium/Main.fs b/spike/scriptorium/Main.fs deleted file mode 100644 index 86ac87c..0000000 --- a/spike/scriptorium/Main.fs +++ /dev/null @@ -1,77 +0,0 @@ -module Main - -open Scriptorium.Nib.Assertion -open type Scriptorium.Quill.Test -open type Scriptorium.Quill.Runner - -open Fable.Beam.Maps - -/// Spike: run the Scriptorium test framework (Nib assertions + the Quill runner) on the BEAM, -/// against real Fable.Beam bindings. -/// -/// Unlike test/, there is no Erlang test_runner.erl and no [] marker: Quill's runner *is* -/// the entry point. Fable emits [] as main:main/1, Quill runs the suite and halts the -/// VM with the exit code, so `erl` returns non-zero on failure. -let tests = - [ testList ( - "Nib assertions on the BEAM", - [ test ("isEqualTo", fun _ -> assertThat 42 (isEqualTo 42)) - - test ("chained comparisons", fun _ -> assertThat 42 (isGreaterThan 40 >> isLessThan 50)) - - // F# strings compile to Erlang binaries (<<"hello">>), not charlists. - test ("strings", fun _ -> assertThat "hello" (isEqualTo "hello")) - - test ("booleans", fun _ -> assertThat true isTrue) - - test ("lists", fun _ -> assertThat [ 1; 2; 3 ] (hasSize 3 >> contain 2)) - - // Structural equality across the Fable runtime, not just primitives. - test ("options", fun _ -> assertThat (Some "cowboy") (isEqualTo (Some "cowboy"))) ] - ) - - testList ( - "Fable.Beam maps bindings", - [ test ( - "maps.new_ creates an empty map", - fun _ -> - let m: BeamMap = maps.new_ () - assertThat (maps.size m) (isEqualTo 0) - ) - - test ( - "maps.put and maps.get round-trip", - fun _ -> - let m: BeamMap = maps.new_ () - let m = maps.put ("key", "value", m) - assertThat (maps.get ("key", m)) (isEqualTo "value") - ) - - test ( - "maps.get falls back to the default for a missing key", - fun _ -> - let m: BeamMap = maps.new_ () - assertThat (maps.get ("missing", m, 42)) (isEqualTo 42) - ) - - test ( - "tryFind maps presence onto an option", - fun _ -> - let m: BeamMap = maps.put ("x", 99, maps.new_ ()) - assertThat (tryFind "x" m) (isEqualTo (Some 99)) - assertThat (tryFind "nope" m) (isEqualTo None) - ) - - test ( - "ofList builds a map from a literal list", - fun _ -> - let headers: BeamMap = - ofList [ "content-type", "text/html"; "server", "cowboy" ] - - assertThat (maps.size headers) (isEqualTo 2) - assertThat (tryFind "server" headers) (isEqualTo (Some "cowboy")) - ) ] - ) ] - -[] -let main _ = runTests tests diff --git a/spike/scriptorium/Spike.fsproj b/spike/scriptorium/Spike.fsproj deleted file mode 100644 index e8b9288..0000000 --- a/spike/scriptorium/Spike.fsproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net10.0 - Exe - preview - false - NU1510;NU1701 - - - - - - - - - - - diff --git a/test/Fable.Beam.Test.fsproj b/test/Fable.Beam.Test.fsproj index 96a1a14..6c71e6d 100644 --- a/test/Fable.Beam.Test.fsproj +++ b/test/Fable.Beam.Test.fsproj @@ -4,7 +4,6 @@ net10.0 false false - true preview Exe NU1510 @@ -14,34 +13,44 @@ + + + + + + + - - - - - - - - + + + + + - + + - - + + + - + + - + + + - - - - + \ No newline at end of file diff --git a/test/Main.fs b/test/Main.fs index 3f16adb..6c66750 100644 --- a/test/Main.fs +++ b/test/Main.fs @@ -1,9 +1,38 @@ -#if FABLE_COMPILER -module Program +module Fable.Beam.Tests.Main -() -#else -module Program = - [] - let main _ = 0 -#endif +open Scriptorium.Quill +open type Scriptorium.Quill.Runner + +// Quill is the entry point here (not an Erlang test_runner): Fable emits this as main:main/1, and +// Quill runs the registered suites then halts the VM with its exit code -- non-zero on failure. +// This is a BEAM-only subset for now: only the modules converted off [] are registered. +// Add more `.tests` below as each remaining file migrates over to Scriptorium. +[] +let main _ = + runTests + [ Timer.tests + Maps.tests + GenServer.tests + Base64.tests + Math.tests + Io.tests + IoLib.tests + Rand.tests + Proplists.tests + Binary.tests + Calendar.tests + Queue.tests + Lists.tests + String.tests + Erlang.tests + Re.tests + Dynamic.tests + UriString.tests + Callbacks.tests + Os.tests + Port.tests + Supervisor.tests + Logger.tests + File.tests + Ets.tests + Jsx.tests ] \ No newline at end of file diff --git a/test/TestBase64.fs b/test/TestBase64.fs index 3f4ec72..1979d3e 100644 --- a/test/TestBase64.fs +++ b/test/TestBase64.fs @@ -1,102 +1,53 @@ module Fable.Beam.Tests.Base64 -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.Base64 -#endif -[] -let ``test base64.encode produces non-empty string`` () = -#if FABLE_COMPILER - let encoded = base64.encode "hello" - (encoded.Length > 0) |> equal true -#else - () -#endif - -[] -let ``test base64.encode of hello`` () = -#if FABLE_COMPILER - base64.encode "hello" |> equal "aGVsbG8=" -#else - () -#endif - -[] -let ``test base64.encode of empty string`` () = -#if FABLE_COMPILER - base64.encode "" |> equal "" -#else - () -#endif - -[] -let ``test base64.decode reverses encode`` () = -#if FABLE_COMPILER - let original = "hello world" - let encoded = base64.encode original - let decoded = base64.decode encoded - decoded |> equal original -#else - () -#endif - -[] -let ``test base64.decode of known value`` () = -#if FABLE_COMPILER - base64.decode "aGVsbG8=" |> equal "hello" -#else - () -#endif - -[] -let ``test base64.encode decode roundtrip with binary data`` () = -#if FABLE_COMPILER - let data = "Fable.Beam rocks!" - let encoded = base64.encode data - let decoded = base64.decode encoded - decoded |> equal data -#else - () -#endif - -[] -let ``test base64.mime_decode handles whitespace`` () = -#if FABLE_COMPILER - // MIME base64 tolerates embedded whitespace - let encoded = base64.encode "hello" - let decoded = base64.mime_decode encoded - decoded |> equal "hello" -#else - () -#endif - -[] -let ``test tryDecode returns Some for valid base64`` () = -#if FABLE_COMPILER - let result = tryDecode "aGVsbG8=" - result |> equal (Some "hello") -#else - () -#endif - -[] -let ``test tryDecode returns None for invalid base64`` () = -#if FABLE_COMPILER - let result = tryDecode "not!valid@base64#" - result |> equal None -#else - () -#endif - -[] -let ``test tryMimeDecode returns Some for valid input`` () = -#if FABLE_COMPILER - let result = tryMimeDecode "aGVsbG8=" - result |> equal (Some "hello") -#else - () -#endif +let tests = + testList ( + "Base64", + [ test ("encode produces non-empty string", fun _ -> + let encoded = base64.encode "hello" + assertThat (encoded.Length > 0) (isTrue)) + + test ("encode of hello", fun _ -> assertThat (base64.encode "hello") (isEqualTo "aGVsbG8=")) + + test ("encode of empty string", fun _ -> assertThat (base64.encode "") (isEqualTo "")) + + test ("decode reverses encode", fun _ -> + let original = "hello world" + let encoded = base64.encode original + let decoded = base64.decode encoded + assertThat decoded (isEqualTo original)) + + test ("decode of known value", fun _ -> + assertThat (base64.decode "aGVsbG8=") (isEqualTo "hello")) + + test ("encode decode roundtrip with binary data", fun _ -> + let data = "Fable.Beam rocks!" + let encoded = base64.encode data + let decoded = base64.decode encoded + assertThat decoded (isEqualTo data)) + + test ("mime_decode handles whitespace", fun _ -> + let encoded = base64.encode "hello" + let decoded = base64.mime_decode encoded + assertThat decoded (isEqualTo "hello")) + + test ("tryDecode returns Some for valid base64", fun _ -> + let result = tryDecode "aGVsbG8=" + assertThat result (isEqualTo (Some "hello"))) + + test ("tryDecode returns None for invalid base64", fun _ -> + let result = tryDecode "not!valid@base64#" + assertThat result (isEqualTo None)) + + test ("tryMimeDecode returns Some for valid input", fun _ -> + let result = tryMimeDecode "aGVsbG8=" + assertThat result (isEqualTo (Some "hello"))) ] + ) diff --git a/test/TestBinary.fs b/test/TestBinary.fs index 70cd0d2..24bba9f 100644 --- a/test/TestBinary.fs +++ b/test/TestBinary.fs @@ -1,247 +1,136 @@ module Fable.Beam.Tests.Binary -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.Binary open Fable.Beam.Lists -#endif - -[] -let ``test binary.copy makes a copy`` () = -#if FABLE_COMPILER - let b = "hello" - binary.copy b |> equal "hello" -#else - () -#endif - -[] -let ``test binary.copy N times concatenates`` () = -#if FABLE_COMPILER - binary.copy ("ab", 3) |> equal "ababab" -#else - () -#endif - -[] -let ``test binary.at returns byte at position`` () = -#if FABLE_COMPILER - // 'A' = 65, 'B' = 66 - binary.at ("AB", 0) |> equal 65 - binary.at ("AB", 1) |> equal 66 -#else - () -#endif - -[] -let ``test binary.first returns first byte`` () = -#if FABLE_COMPILER - // 'h' = 104 - binary.first "hello" |> equal 104 -#else - () -#endif - -[] -let ``test binary.last returns last byte`` () = -#if FABLE_COMPILER - // 'o' = 111 - binary.last "hello" |> equal 111 -#else - () -#endif - -[] -let ``test binary.part extracts subbinary`` () = -#if FABLE_COMPILER - binary.part ("hello world", 6, 5) |> equal "world" -#else - () -#endif - -[] -let ``test matchFirst returns Some on match`` () = -#if FABLE_COMPILER - matchFirst "hello" "ll" |> equal (Some(2, 2)) -#else - () -#endif - -[] -let ``test matchFirst returns None when not found`` () = -#if FABLE_COMPILER - matchFirst "hello" "xyz" |> equal None -#else - () -#endif - -[] -let ``test matchAll returns all occurrences`` () = -#if FABLE_COMPILER - let results = matchAll "abcabc" "b" - Array.length results |> equal 2 -#else - () -#endif - -[] -let ``test splitFirst splits on first occurrence`` () = -#if FABLE_COMPILER - let parts = splitFirst "hello world" " " - Array.length parts |> equal 2 - parts.[0] |> equal "hello" - parts.[1] |> equal "world" -#else - () -#endif - -[] -let ``test splitAll splits on all occurrences`` () = -#if FABLE_COMPILER - let parts = splitAll "a,b,c" "," - Array.length parts |> equal 3 - parts.[0] |> equal "a" - parts.[1] |> equal "b" - parts.[2] |> equal "c" -#else - () -#endif - -[] -let ``test replaceFirst replaces first occurrence`` () = -#if FABLE_COMPILER - replaceFirst "aabbaa" "aa" "XX" |> equal "XXbbaa" -#else - () -#endif - -[] -let ``test replaceAll replaces all occurrences`` () = -#if FABLE_COMPILER - replaceAll "aabbaa" "aa" "XX" |> equal "XXbbXX" -#else - () -#endif - -[] -let ``test binary.longest_common_prefix`` () = -#if FABLE_COMPILER - // "foo" is the longest prefix common to *all three* ("foobar"/"foobaz" share "fooba", - // but "fooqux" diverges at the 4th byte). - binary.longest_common_prefix ([ "foobar"; "foobaz"; "fooqux" ]) |> equal 3 -#else - () -#endif - -[] -let ``test binary.longest_common_suffix`` () = -#if FABLE_COMPILER - binary.longest_common_suffix ([ "foobar"; "bazbar"; "quuxbar" ]) |> equal 3 -#else - () -#endif - -[] -let ``test binary.bin_to_list returns list of bytes`` () = -#if FABLE_COMPILER - // "ABC" = [65, 66, 67] - let bytes = binary.bin_to_list "ABC" - lists.nth (1, bytes) |> equal 65 - lists.nth (2, bytes) |> equal 66 - lists.nth (3, bytes) |> equal 67 -#else - () -#endif - -[] -let ``test binary.list_to_bin converts bytes to binary`` () = -#if FABLE_COMPILER - // [104, 105] = "hi" - let bytes: BeamList = emitErlExpr () "[104, 105]" - binary.list_to_bin bytes |> equal "hi" -#else - () -#endif - -[] -let ``test binary.bin_to_list and list_to_bin roundtrip`` () = -#if FABLE_COMPILER - let original = "hello" - let bytes = binary.bin_to_list original - binary.list_to_bin bytes |> equal original -#else - () -#endif - -[] -let ``test binary.encode_unsigned and decode_unsigned roundtrip`` () = -#if FABLE_COMPILER - let n = 12345 - let encoded = binary.encode_unsigned n - binary.decode_unsigned encoded |> equal n -#else - () -#endif - -[] -let ``test binary.encode_unsigned of zero roundtrips`` () = -#if FABLE_COMPILER - let encoded = binary.encode_unsigned 0 - binary.decode_unsigned encoded |> equal 0 -#else - () -#endif - -[] -let ``test binary.decode_unsigned with little endian`` () = -#if FABLE_COMPILER - let little = Erlang.binaryToAtom "little" - let big = Erlang.binaryToAtom "big" - // Big-endian encoding of 256 is <<1, 0>>. - // Decoded as little-endian, those bytes read as 1. - let encoded_big = binary.encode_unsigned (256, big) - binary.decode_unsigned (encoded_big, little) |> equal 1 - // Roundtrip via little endian preserves the value. - let encoded_little = binary.encode_unsigned (256, little) - binary.decode_unsigned (encoded_little, little) |> equal 256 -#else - () -#endif - -[] -let ``test binary.referenced_byte_size is at least the logical size`` () = -#if FABLE_COMPILER - // referenced_byte_size reports the size of the *underlying* memory a (sub-)binary points into, - // which OTP's own docs call "a hint for optimization, not exact": for a plain binary it varies - // with how the binary was constructed and the OTP release (5 on OTP 25, 40 on OTP 27, 256 for a - // shell literal). The only portable guarantee is that it references at least what it contains. - let s = "hello" - (binary.referenced_byte_size s >= Erlang.byteSize s) |> equal true - (binary.referenced_byte_size "" >= 0) |> equal true -#else - () -#endif - -[] -let ``test binary.splitAllRaw returns the native list form of splitAll`` () = -#if FABLE_COMPILER - let parts: BeamList = splitAllRaw "a-b-c" "-" - let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b\">>, <<\"c\">>]" - parts |> equal expected -#else - () -#endif - -[] -let ``test binary.splitFirstRaw returns the native list form of splitFirst`` () = -#if FABLE_COMPILER - let parts: BeamList = splitFirstRaw "a-b-c" "-" - let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b-c\">>]" - parts |> equal expected -#else - () -#endif + +let tests = + testList ( + "Binary", + [ test ("copy makes a copy", fun _ -> + let b = "hello" + assertThat (binary.copy b) (isEqualTo "hello")) + + test ("copy N times concatenates", fun _ -> + assertThat (binary.copy ("ab", 3)) (isEqualTo "ababab")) + + test ("at returns byte at position", fun _ -> + // 'A' = 65, 'B' = 66 + assertThat (binary.at ("AB", 0)) (isEqualTo 65) + assertThat (binary.at ("AB", 1)) (isEqualTo 66) + ) + + test ("first returns first byte", fun _ -> + // 'h' = 104 + assertThat (binary.first "hello") (isEqualTo 104)) + + test ("last returns last byte", fun _ -> + // 'o' = 111 + assertThat (binary.last "hello") (isEqualTo 111)) + + test ("part extracts subbinary", fun _ -> + assertThat (binary.part ("hello world", 6, 5)) (isEqualTo "world")) + + test ("matchFirst returns Some on match", fun _ -> + assertThat (matchFirst "hello" "ll") (isEqualTo (Some (2, 2)))) + + test ("matchFirst returns None when not found", fun _ -> + assertThat (matchFirst "hello" "xyz") (isEqualTo None)) + + test ("matchAll returns all occurrences", fun _ -> + let results = matchAll "abcabc" "b" + assertThat (Array.length results) (isEqualTo 2)) + + test ("splitFirst splits on first occurrence", fun _ -> + let parts = splitFirst "hello world" " " + assertThat (Array.length parts) (isEqualTo 2) + assertThat (parts.[0]) (isEqualTo "hello") + assertThat (parts.[1]) (isEqualTo "world") + ) + + test ("splitAll splits on all occurrences", fun _ -> + let parts = splitAll "a,b,c" "," + assertThat (Array.length parts) (isEqualTo 3) + assertThat (parts.[0]) (isEqualTo "a") + assertThat (parts.[1]) (isEqualTo "b") + assertThat (parts.[2]) (isEqualTo "c") + ) + + test ("replaceFirst replaces first occurrence", fun _ -> + assertThat (replaceFirst "aabbaa" "aa" "XX") (isEqualTo "XXbbaa")) + + test ("replaceAll replaces all occurrences", fun _ -> + assertThat (replaceAll "aabbaa" "aa" "XX") (isEqualTo "XXbbXX")) + + test ("longest_common_prefix", fun _ -> + // "foo" is the longest prefix common to *all three* ("foobar"/"foobaz" share "fooba", + // but "fooqux" diverges at the 4th byte). + assertThat (binary.longest_common_prefix ([ "foobar"; "foobaz"; "fooqux" ])) (isEqualTo 3)) + + test ("longest_common_suffix", fun _ -> + assertThat (binary.longest_common_suffix ([ "foobar"; "bazbar"; "quuxbar" ])) (isEqualTo 3)) + + test ("bin_to_list returns list of bytes", fun _ -> + // "ABC" = [65, 66, 67] + let bytes = binary.bin_to_list "ABC" + assertThat (lists.nth (1, bytes)) (isEqualTo 65) + assertThat (lists.nth (2, bytes)) (isEqualTo 66) + assertThat (lists.nth (3, bytes)) (isEqualTo 67) + ) + + test ("list_to_bin converts bytes to binary", fun _ -> + // [104, 105] = "hi" + let bytes: BeamList = emitErlExpr () "[104, 105]" + assertThat (binary.list_to_bin bytes) (isEqualTo "hi")) + + test ("bin_to_list and list_to_bin roundtrip", fun _ -> + let original = "hello" + let bytes = binary.bin_to_list original + assertThat (binary.list_to_bin bytes) (isEqualTo original)) + + test ("encode_unsigned and decode_unsigned roundtrip", fun _ -> + let n = 12345 + let encoded = binary.encode_unsigned n + assertThat (binary.decode_unsigned encoded) (isEqualTo n)) + + test ("encode_unsigned of zero roundtrips", fun _ -> + let encoded = binary.encode_unsigned 0 + assertThat (binary.decode_unsigned encoded) (isEqualTo 0)) + + test ("decode_unsigned with little endian", fun _ -> + let little = Erlang.binaryToAtom "little" + let big = Erlang.binaryToAtom "big" + // Big-endian encoding of 256 is <<1, 0>>. + // Decoded as little-endian, those bytes read as 1. + let encoded_big = binary.encode_unsigned (256, big) + assertThat (binary.decode_unsigned (encoded_big, little)) (isEqualTo 1) + // Roundtrip via little endian preserves the value. + let encoded_little = binary.encode_unsigned (256, little) + assertThat (binary.decode_unsigned (encoded_little, little)) (isEqualTo 256) + ) + + test ("referenced_byte_size is at least the logical size", fun _ -> + // referenced_byte_size reports the size of the *underlying* memory a (sub-)binary points into, + // which OTP's own docs call "a hint for optimization, not exact": for a plain binary it varies + // with how the binary was constructed and the OTP release (5 on OTP 25, 40 on OTP 27, 256 for a + // shell literal). The only portable guarantee is that it references at least what it contains. + let s = "hello" + assertThat (binary.referenced_byte_size s >= Erlang.byteSize s) (isTrue) + assertThat (binary.referenced_byte_size "" >= 0) (isTrue)) + + test ("splitAllRaw returns the native list form of splitAll", fun _ -> + let parts: BeamList = splitAllRaw "a-b-c" "-" + let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b\">>, <<\"c\">>]" + assertThat parts (isEqualTo expected)) + + test ("splitFirstRaw returns the native list form of splitFirst", fun _ -> + let parts: BeamList = splitFirstRaw "a-b-c" "-" + let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b-c\">>]" + assertThat parts (isEqualTo expected)) ] + ) diff --git a/test/TestCalendar.fs b/test/TestCalendar.fs index 9019bfc..a6c0b37 100644 --- a/test/TestCalendar.fs +++ b/test/TestCalendar.fs @@ -1,364 +1,181 @@ module Fable.Beam.Tests.Calendar -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.Calendar -#endif - -// ============================================================================ -// is_leap_year -// ============================================================================ - -[] -let ``test calendar.is_leap_year returns true for 2000`` () = -#if FABLE_COMPILER - calendar.is_leap_year 2000 |> equal true -#else - () -#endif - -[] -let ``test calendar.is_leap_year returns true for 2024`` () = -#if FABLE_COMPILER - calendar.is_leap_year 2024 |> equal true -#else - () -#endif - -[] -let ``test calendar.is_leap_year returns false for 1900`` () = -#if FABLE_COMPILER - // 1900 is divisible by 100 but not 400 — not a leap year - calendar.is_leap_year 1900 |> equal false -#else - () -#endif - -[] -let ``test calendar.is_leap_year returns false for 2023`` () = -#if FABLE_COMPILER - calendar.is_leap_year 2023 |> equal false -#else - () -#endif - -// ============================================================================ -// last_day_of_the_month -// ============================================================================ - -[] -let ``test calendar.last_day_of_the_month returns 31 for January`` () = -#if FABLE_COMPILER - calendar.last_day_of_the_month (2024, 1) |> equal 31 -#else - () -#endif - -[] -let ``test calendar.last_day_of_the_month returns 29 for February in leap year`` () = -#if FABLE_COMPILER - calendar.last_day_of_the_month (2024, 2) |> equal 29 -#else - () -#endif - -[] -let ``test calendar.last_day_of_the_month returns 28 for February in non-leap year`` () = -#if FABLE_COMPILER - calendar.last_day_of_the_month (2023, 2) |> equal 28 -#else - () -#endif - -[] -let ``test calendar.last_day_of_the_month returns 30 for April`` () = -#if FABLE_COMPILER - calendar.last_day_of_the_month (2024, 4) |> equal 30 -#else - () -#endif - -// ============================================================================ -// day_of_the_week -// ============================================================================ - -[] -let ``test calendar.day_of_the_week returns 1 for Monday`` () = -#if FABLE_COMPILER - // 2024-01-01 is a Monday - calendar.day_of_the_week (2024, 1, 1) |> equal 1 -#else - () -#endif - -[] -let ``test calendar.day_of_the_week returns 7 for Sunday`` () = -#if FABLE_COMPILER - // 2024-01-07 is a Sunday - calendar.day_of_the_week (2024, 1, 7) |> equal 7 -#else - () -#endif - -[] -let ``test calendar.day_of_the_week returns 5 for Friday`` () = -#if FABLE_COMPILER - // 2024-01-05 is a Friday - calendar.day_of_the_week (2024, 1, 5) |> equal 5 -#else - () -#endif - -// ============================================================================ -// date_to_gregorian_days / gregorian_days_to_date roundtrip -// ============================================================================ - -[] -let ``test calendar.date_to_gregorian_days for known date`` () = -#if FABLE_COMPILER - // Erlang epoch: 0000-01-01. Days to 2000-01-01 = 730485 - calendar.date_to_gregorian_days (2000, 1, 1) |> equal 730485 -#else - () -#endif - -[] -let ``test calendar.gregorian_days_to_date roundtrip`` () = -#if FABLE_COMPILER - let days = calendar.date_to_gregorian_days (2024, 3, 15) - let (y, m, d) = calendar.gregorian_days_to_date days - y |> equal 2024 - m |> equal 3 - d |> equal 15 -#else - () -#endif - -[] -let ``test calendar.gregorian_days_to_date for known days`` () = -#if FABLE_COMPILER - let (y, m, d) = calendar.gregorian_days_to_date 730485 - y |> equal 2000 - m |> equal 1 - d |> equal 1 -#else - () -#endif - -// ============================================================================ -// timeToSeconds / secondsToTime roundtrip -// ============================================================================ - -[] -let ``test calendar.timeToSeconds midnight is zero`` () = -#if FABLE_COMPILER - timeToSeconds (0, 0, 0) |> equal 0 -#else - () -#endif - -[] -let ``test calendar.timeToSeconds for noon`` () = -#if FABLE_COMPILER - // 12:00:00 = 12 * 3600 = 43200 seconds - timeToSeconds (12, 0, 0) |> equal 43200 -#else - () -#endif - -[] -let ``test calendar.timeToSeconds for 1:30:30`` () = -#if FABLE_COMPILER - // 1*3600 + 30*60 + 30 = 5430 - timeToSeconds (1, 30, 30) |> equal 5430 -#else - () -#endif - -[] -let ``test calendar.secondsToTime roundtrip`` () = -#if FABLE_COMPILER - let (h, m, s) = secondsToTime 5430 - h |> equal 1 - m |> equal 30 - s |> equal 30 -#else - () -#endif - -[] -let ``test calendar.secondsToTime for noon`` () = -#if FABLE_COMPILER - let (h, m, s) = secondsToTime 43200 - h |> equal 12 - m |> equal 0 - s |> equal 0 -#else - () -#endif - -// ============================================================================ -// datetimeToGregorianSeconds / gregorian_seconds_to_datetime roundtrip -// ============================================================================ - -[] -let ``test calendar.datetimeToGregorianSeconds and back roundtrip`` () = -#if FABLE_COMPILER - let dt: DateTime = (2024, 3, 15), (10, 30, 0) - let secs = datetimeToGregorianSeconds dt - let ((y, mo, d), (h, mi, s)) = calendar.gregorian_seconds_to_datetime secs - y |> equal 2024 - mo |> equal 3 - d |> equal 15 - h |> equal 10 - mi |> equal 30 - s |> equal 0 -#else - () -#endif - -[] -let ``test calendar.datetimeToGregorianSeconds for known value`` () = -#if FABLE_COMPILER - // 2000-01-01 00:00:00 = 730485 days * 86400 s/day = 63113904000 - let secs = datetimeToGregorianSeconds ((2000, 1, 1), (0, 0, 0)) - secs |> equal 63113904000L -#else - () -#endif - -// ============================================================================ -// local_time / universal_time (sanity checks) -// ---------------------------------------------------------------------------- -// `calendar:date/0` and `calendar:time/0` do NOT exist in the calendar module -// (they live in `erlang`), so no tests for them here. -// ============================================================================ - -[] -let ``test calendar.local_time returns plausible datetime`` () = -#if FABLE_COMPILER - let ((y, mo, d), (h, mi, s)) = calendar.local_time () - (y >= 2024) |> equal true - (mo >= 1 && mo <= 12) |> equal true - (d >= 1 && d <= 31) |> equal true - (h >= 0 && h <= 23) |> equal true - (mi >= 0 && mi <= 59) |> equal true - (s >= 0 && s <= 60) |> equal true -#else - () -#endif - -[] -let ``test calendar.universal_time returns plausible datetime`` () = -#if FABLE_COMPILER - let ((y, _, _), _) = calendar.universal_time () - (y >= 2024) |> equal true -#else - () -#endif - -// ============================================================================ -// localTimeToUniversalTime / universalTimeToLocalTime smoke tests -// ---------------------------------------------------------------------------- -// Result depends on the system's time zone, so we only assert structural -// validity (year preserved within ±1, month/day/hour/minute/second in range). -// ============================================================================ - -[] -let ``test calendar.localTimeToUniversalTime returns plausible datetime`` () = -#if FABLE_COMPILER - let ((y, mo, d), (h, mi, s)) = localTimeToUniversalTime ((2024, 6, 15), (12, 0, 0)) - // Crossing tz can shift the date by one day, so we allow the year to differ by 1. - (y >= 2023 && y <= 2025) |> equal true - (mo >= 1 && mo <= 12) |> equal true - (d >= 1 && d <= 31) |> equal true - (h >= 0 && h <= 23) |> equal true - (mi >= 0 && mi <= 59) |> equal true - (s >= 0 && s <= 60) |> equal true -#else - () -#endif - -[] -let ``test calendar.universalTimeToLocalTime returns plausible datetime`` () = -#if FABLE_COMPILER - let ((y, mo, d), (h, mi, s)) = universalTimeToLocalTime ((2024, 6, 15), (12, 0, 0)) - (y >= 2023 && y <= 2025) |> equal true - (mo >= 1 && mo <= 12) |> equal true - (d >= 1 && d <= 31) |> equal true - (h >= 0 && h <= 23) |> equal true - (mi >= 0 && mi <= 59) |> equal true - (s >= 0 && s <= 60) |> equal true -#else - () -#endif - -[] -let ``test calendar.localTimeToUniversalTime then back roundtrips`` () = -#if FABLE_COMPILER - let original: DateTime = (2024, 6, 15), (12, 0, 0) - let utc = localTimeToUniversalTime original - let roundtrip = universalTimeToLocalTime utc - roundtrip |> equal original -#else - () -#endif - -// ============================================================================ -// system_time_to_universal_time / system_time_to_local_time -// ============================================================================ - -[] -let ``test calendar.system_time_to_universal_time for unix epoch`` () = -#if FABLE_COMPILER - // Unix epoch 0 seconds = 1970-01-01 00:00:00 UTC - let ((y, mo, d), (h, mi, s)) = - calendar.system_time_to_universal_time (0L, TimeUnit.Second) - - y |> equal 1970 - mo |> equal 1 - d |> equal 1 - h |> equal 0 - mi |> equal 0 - s |> equal 0 -#else - () -#endif - -[] -let ``test calendar.system_time_to_universal_time for known second`` () = -#if FABLE_COMPILER - // 1700000000 seconds since the Unix epoch = 2023-11-14 22:13:20 UTC - let ((y, mo, d), (h, mi, s)) = - calendar.system_time_to_universal_time (1700000000L, TimeUnit.Second) - - y |> equal 2023 - mo |> equal 11 - d |> equal 14 - h |> equal 22 - mi |> equal 13 - s |> equal 20 -#else - () -#endif - -[] -let ``test calendar.system_time_to_local_time returns plausible datetime`` () = -#if FABLE_COMPILER - // Local time depends on the system time zone, so only assert structural validity. - let ((y, mo, d), (h, mi, s)) = - calendar.system_time_to_local_time (1700000000L, TimeUnit.Second) - - (y >= 2023 && y <= 2024) |> equal true - (mo >= 1 && mo <= 12) |> equal true - (d >= 1 && d <= 31) |> equal true - (h >= 0 && h <= 23) |> equal true - (mi >= 0 && mi <= 59) |> equal true - (s >= 0 && s <= 60) |> equal true -#else - () -#endif + +let tests = + testList ( + "Calendar", + [ test ("is_leap_year returns true for 2000", fun _ -> + assertThat (calendar.is_leap_year 2000) (isTrue)) + + test ("is_leap_year returns true for 2024", fun _ -> + assertThat (calendar.is_leap_year 2024) (isTrue)) + + test ("is_leap_year returns false for 1900", fun _ -> + // 1900 is divisible by 100 but not 400 — not a leap year + assertThat (calendar.is_leap_year 1900) (isFalse)) + + test ("is_leap_year returns false for 2023", fun _ -> + assertThat (calendar.is_leap_year 2023) (isFalse)) + + test ("last_day_of_the_month returns 31 for January", fun _ -> + assertThat (calendar.last_day_of_the_month (2024, 1)) (isEqualTo 31)) + + test ("last_day_of_the_month returns 29 for February in leap year", fun _ -> + assertThat (calendar.last_day_of_the_month (2024, 2)) (isEqualTo 29)) + + test ("last_day_of_the_month returns 28 for February in non-leap year", fun _ -> + assertThat (calendar.last_day_of_the_month (2023, 2)) (isEqualTo 28)) + + test ("last_day_of_the_month returns 30 for April", fun _ -> + assertThat (calendar.last_day_of_the_month (2024, 4)) (isEqualTo 30)) + + test ("day_of_the_week returns 1 for Monday", fun _ -> + // 2024-01-01 is a Monday + assertThat (calendar.day_of_the_week (2024, 1, 1)) (isEqualTo 1)) + + test ("day_of_the_week returns 7 for Sunday", fun _ -> + // 2024-01-07 is a Sunday + assertThat (calendar.day_of_the_week (2024, 1, 7)) (isEqualTo 7)) + + test ("day_of_the_week returns 5 for Friday", fun _ -> + // 2024-01-05 is a Friday + assertThat (calendar.day_of_the_week (2024, 1, 5)) (isEqualTo 5)) + + test ("date_to_gregorian_days for known date", fun _ -> + // Erlang epoch: 0000-01-01. Days to 2000-01-01 = 730485 + assertThat (calendar.date_to_gregorian_days (2000, 1, 1)) (isEqualTo 730485)) + + test ("gregorian_days_to_date roundtrip", fun _ -> + let days = calendar.date_to_gregorian_days (2024, 3, 15) + let (y, m, d) = calendar.gregorian_days_to_date days + assertThat y (isEqualTo 2024) + assertThat m (isEqualTo 3) + assertThat d (isEqualTo 15)) + + test ("gregorian_days_to_date for known days", fun _ -> + let (y, m, d) = calendar.gregorian_days_to_date 730485 + assertThat y (isEqualTo 2000) + assertThat m (isEqualTo 1) + assertThat d (isEqualTo 1)) + + test ("timeToSeconds midnight is zero", fun _ -> + assertThat (timeToSeconds (0, 0, 0)) (isEqualTo 0)) + + test ("timeToSeconds for noon", fun _ -> + // 12:00:00 = 12 * 3600 = 43200 seconds + assertThat (timeToSeconds (12, 0, 0)) (isEqualTo 43200)) + + test ("timeToSeconds for 1:30:30", fun _ -> + // 1*3600 + 30*60 + 30 = 5430 + assertThat (timeToSeconds (1, 30, 30)) (isEqualTo 5430)) + + test ("secondsToTime roundtrip", fun _ -> + let (h, m, s) = secondsToTime 5430 + assertThat h (isEqualTo 1) + assertThat m (isEqualTo 30) + assertThat s (isEqualTo 30)) + + test ("secondsToTime for noon", fun _ -> + let (h, m, s) = secondsToTime 43200 + assertThat h (isEqualTo 12) + assertThat m (isEqualTo 0) + assertThat s (isEqualTo 0)) + + test ("datetimeToGregorianSeconds and back roundtrip", fun _ -> + let dt: DateTime = (2024, 3, 15), (10, 30, 0) + let secs = datetimeToGregorianSeconds dt + let ((y, mo, d), (h, mi, s)) = calendar.gregorian_seconds_to_datetime secs + assertThat y (isEqualTo 2024) + assertThat mo (isEqualTo 3) + assertThat d (isEqualTo 15) + assertThat h (isEqualTo 10) + assertThat mi (isEqualTo 30) + assertThat s (isEqualTo 0)) + + test ("datetimeToGregorianSeconds for known value", fun _ -> + // 2000-01-01 00:00:00 = 730485 days * 86400 s/day = 63113904000 + let secs = datetimeToGregorianSeconds ((2000, 1, 1), (0, 0, 0)) + assertThat secs (isEqualTo 63113904000L)) + + test ("local_time returns plausible datetime", fun _ -> + let ((y, mo, d), (h, mi, s)) = calendar.local_time () + assertThat (y >= 2024) (isTrue) + assertThat (mo >= 1 && mo <= 12) (isTrue) + assertThat (d >= 1 && d <= 31) (isTrue) + assertThat (h >= 0 && h <= 23) (isTrue) + assertThat (mi >= 0 && mi <= 59) (isTrue) + assertThat (s >= 0 && s <= 60) (isTrue)) + + test ("universal_time returns plausible datetime", fun _ -> + let ((y, _, _), _) = calendar.universal_time () + assertThat (y >= 2024) (isTrue)) + + test ("localTimeToUniversalTime returns plausible datetime", fun _ -> + let ((y, mo, d), (h, mi, s)) = localTimeToUniversalTime ((2024, 6, 15), (12, 0, 0)) + // Crossing tz can shift the date by one day, so we allow the year to differ by 1. + assertThat (y >= 2023 && y <= 2025) (isTrue) + assertThat (mo >= 1 && mo <= 12) (isTrue) + assertThat (d >= 1 && d <= 31) (isTrue) + assertThat (h >= 0 && h <= 23) (isTrue) + assertThat (mi >= 0 && mi <= 59) (isTrue) + assertThat (s >= 0 && s <= 60) (isTrue)) + + test ("universalTimeToLocalTime returns plausible datetime", fun _ -> + let ((y, mo, d), (h, mi, s)) = universalTimeToLocalTime ((2024, 6, 15), (12, 0, 0)) + assertThat (y >= 2023 && y <= 2025) (isTrue) + assertThat (mo >= 1 && mo <= 12) (isTrue) + assertThat (d >= 1 && d <= 31) (isTrue) + assertThat (h >= 0 && h <= 23) (isTrue) + assertThat (mi >= 0 && mi <= 59) (isTrue) + assertThat (s >= 0 && s <= 60) (isTrue)) + + test ("localTimeToUniversalTime then back roundtrips", fun _ -> + let original: DateTime = (2024, 6, 15), (12, 0, 0) + let utc = localTimeToUniversalTime original + let roundtrip = universalTimeToLocalTime utc + assertThat roundtrip (isEqualTo original)) + + test ("system_time_to_universal_time for unix epoch", fun _ -> + // Unix epoch 0 seconds = 1970-01-01 00:00:00 UTC + let ((y, mo, d), (h, mi, s)) = + calendar.system_time_to_universal_time (0L, TimeUnit.Second) + + assertThat y (isEqualTo 1970) + assertThat mo (isEqualTo 1) + assertThat d (isEqualTo 1) + assertThat h (isEqualTo 0) + assertThat mi (isEqualTo 0) + assertThat s (isEqualTo 0)) + + test ("system_time_to_universal_time for known second", fun _ -> + // 1700000000 seconds since the Unix epoch = 2023-11-14 22:13:20 UTC + let ((y, mo, d), (h, mi, s)) = + calendar.system_time_to_universal_time (1700000000L, TimeUnit.Second) + + assertThat y (isEqualTo 2023) + assertThat mo (isEqualTo 11) + assertThat d (isEqualTo 14) + assertThat h (isEqualTo 22) + assertThat mi (isEqualTo 13) + assertThat s (isEqualTo 20)) + + test ("system_time_to_local_time returns plausible datetime", fun _ -> + // Local time depends on the system time zone, so only assert structural validity. + let ((y, mo, d), (h, mi, s)) = + calendar.system_time_to_local_time (1700000000L, TimeUnit.Second) + + assertThat (y >= 2023 && y <= 2024) (isTrue) + assertThat (mo >= 1 && mo <= 12) (isTrue) + assertThat (d >= 1 && d <= 31) (isTrue) + assertThat (h >= 0 && h <= 23) (isTrue) + assertThat (mi >= 0 && mi <= 59) (isTrue) + assertThat (s >= 0 && s <= 60) (isTrue)) ] + ) diff --git a/test/TestCallbacks.fs b/test/TestCallbacks.fs index 4a954c5..418a6a5 100644 --- a/test/TestCallbacks.fs +++ b/test/TestCallbacks.fs @@ -8,13 +8,15 @@ /// `badarity` and the guide's recommendation needs revisiting. module Fable.Beam.Tests.Callbacks -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam +#if FABLE_COMPILER /// Curried 2-argument callback, handed straight to lists:foldl/3. [] let private foldlCurried (f: 'T -> 'Acc -> 'Acc) (acc: 'Acc) (l: Lists.BeamList<'T>) : 'Acc = nativeOnly @@ -47,70 +49,35 @@ let private add3 (a: int) (b: int) (c: int) : int = a + b + c let private nums () : Lists.BeamList = emitErlExpr () "[1, 2, 3]" #endif -[] -let ``test curried lambda literal reaches foldl as a 2-arity fun`` () = -#if FABLE_COMPILER - foldlCurried (fun x acc -> x + acc) 0 (nums ()) |> equal 6 -#else - () -#endif +let tests = + testList ( + "Callbacks", + [ test ("curried lambda literal reaches foldl as a 2-arity fun", fun _ -> + assertThat (foldlCurried (fun x acc -> x + acc) 0 (nums ())) (isEqualTo 6)) -[] -let ``test System.Func callback behaves identically to the curried form`` () = -#if FABLE_COMPILER - foldlFunc (System.Func<_, _, _>(fun x acc -> x + acc)) 0 (nums ()) |> equal 6 -#else - () -#endif + test ("System.Func callback behaves identically to the curried form", fun _ -> + assertThat (foldlFunc (System.Func<_, _, _>(fun x acc -> x + acc)) 0 (nums ())) (isEqualTo 6)) -[] -let ``test named curried function reaches foldl as a 2-arity fun`` () = -#if FABLE_COMPILER - foldlCurried addFn 0 (nums ()) |> equal 6 -#else - () -#endif + test ("named curried function reaches foldl as a 2-arity fun", fun _ -> + assertThat (foldlCurried addFn 0 (nums ())) (isEqualTo 6)) -[] -let ``test curried function returned from a function keeps its arity`` () = -#if FABLE_COMPILER - // Arity is not syntactically visible at the call site. - foldlCurried (makeAdder ()) 0 (nums ()) |> equal 6 -#else - () -#endif + test ("curried function returned from a function keeps its arity", fun _ -> + // Arity is not syntactically visible at the call site. + assertThat (foldlCurried (makeAdder ()) 0 (nums ())) (isEqualTo 6)) -[] -let ``test partially applied function passes its remaining arity`` () = -#if FABLE_COMPILER - // add3 10 has two arguments left, so it must arrive as a 2-arity fun. - foldlCurried (add3 10) 0 (nums ()) |> equal 36 -#else - () -#endif + test ("partially applied function passes its remaining arity", fun _ -> + // add3 10 has two arguments left, so it must arrive as a 2-arity fun. + assertThat (foldlCurried (add3 10) 0 (nums ())) (isEqualTo 36)) -[] -let ``test callback boxed through an obj-typed parameter keeps its arity`` () = -#if FABLE_COMPILER - foldlObj (box (fun x acc -> x + acc)) 0 (nums ()) |> equal 6 -#else - () -#endif + test ("callback boxed through an obj-typed parameter keeps its arity", fun _ -> + assertThat (foldlObj (box (fun x acc -> x + acc)) 0 (nums ())) (isEqualTo 6)) -[] -let ``test ImportAll interface member takes a curried 2-arg callback`` () = -#if FABLE_COMPILER - probeLists.foldl ((fun x acc -> x + acc), 0, nums ()) |> equal 6 -#else - () -#endif + test ("ImportAll interface member takes a curried 2-arg callback", fun _ -> + assertThat (probeLists.foldl ((fun x acc -> x + acc), 0, nums ())) (isEqualTo 6)) -[] -let ``test ImportAll interface member takes a curried 1-arg callback`` () = -#if FABLE_COMPILER - let kept = probeLists.filter ((fun x -> x > 1), nums ()) - let n: int = emitErlExpr kept "erlang:length($0)" - n |> equal 2 -#else - () -#endif + test ("ImportAll interface member takes a curried 1-arg callback", fun _ -> + let kept = probeLists.filter ((fun x -> x > 1), nums ()) + let n: int = emitErlExpr kept "erlang:length($0)" + assertThat n (isEqualTo 2) + ) ] + ) diff --git a/test/TestDynamic.fs b/test/TestDynamic.fs index faebe67..aff30ba 100644 --- a/test/TestDynamic.fs +++ b/test/TestDynamic.fs @@ -1,199 +1,138 @@ module Fable.Beam.Tests.Dynamic -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam -#endif - -[] -let ``test Decode.int succeeds on integer`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "42" - - match Decode.int d with - | Ok v -> v |> equal 42 - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.int fails on non-integer`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "<<\"hello\">>" - - match Decode.int d with - | Ok _ -> failwith "expected Error" - | Error _ -> () -#else - () -#endif - -[] -let ``test Decode.string succeeds on binary`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "<<\"hello\">>" - - match Decode.string d with - | Ok v -> v |> equal "hello" - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.atom succeeds on atom`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "ok" - - match Decode.atom d with - | Ok _ -> () // don't compare Atom values across the boundary, just ensure decode succeeded - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.bool succeeds on true`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "true" - - match Decode.bool d with - | Ok v -> v |> equal true - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.field extracts map value`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "#{name => <<\"alice\">>, age => 30}" - let nameKey = Erlang.binaryToAtom "name" - let ageKey = Erlang.binaryToAtom "age" - - match Decode.field nameKey Decode.string d with - | Ok name -> name |> equal "alice" - | Error e -> failwithf "expected Ok name, got Error %s" e - - match Decode.field ageKey Decode.int d with - | Ok age -> age |> equal 30 - | Error e -> failwithf "expected Ok age, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.field errors on missing key`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "#{name => <<\"alice\">>}" - let missingKey = Erlang.binaryToAtom "nonexistent" - - match Decode.field missingKey Decode.string d with - | Ok _ -> failwith "expected Error on missing field" - | Error _ -> () -#else - () -#endif - -[] -let ``test Decode.list decodes homogeneous list`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "[1, 2, 3, 4]" - - match Decode.list Decode.int d with - | Ok arr -> - Array.length arr |> equal 4 - arr.[0] |> equal 1 - arr.[3] |> equal 4 - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.list short-circuits on first decode error`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "[1, 2, <<\"not_int\">>, 4]" - - match Decode.list Decode.int d with - | Ok _ -> failwith "expected Error" - | Error _ -> () -#else - () -#endif - -[] -let ``test Decode.optional returns None for undefined`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "undefined" - - match Decode.optional Decode.int d with - | Ok None -> () - | Ok(Some v) -> failwithf "expected None, got Some %d" v - | Error e -> failwithf "expected Ok None, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.optional returns Some for value`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "42" - - match Decode.optional Decode.int d with - | Ok(Some v) -> v |> equal 42 - | Ok None -> failwith "expected Some" - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.tuple2 decodes a pair`` () = -#if FABLE_COMPILER - let d: Dynamic = emitErlExpr () "{<<\"alice\">>, 30}" - - match Decode.tuple2 Decode.string Decode.int d with - | Ok(name, age) -> - name |> equal "alice" - age |> equal 30 - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif - -[] -let ``test Decode.field with Atom.ofString key finds an atom-keyed field`` () = -#if FABLE_COMPILER - // Regression for the documented decoder example: the key must be a real atom. - // A binary key (what `Atom "name"` used to produce) never matches #{name => ...}. - let d: Dynamic = emitErlExpr () "#{name => <<\"alice\">>, age => 30}" - - match Decode.field (Atom.ofString "name") Decode.string d with - | Ok name -> name |> equal "alice" - | Error e -> failwithf "expected Ok name, got Error %s" e -#else - () -#endif - -[] -let ``test Decode combinators accept a plain lambda decoder`` () = -#if FABLE_COMPILER - // No System.Func wrapper: a single-argument F# function compiles to the - // 1-arity Erlang fun the Emit applies. - let d: Dynamic = emitErlExpr () "[1, 2, 3]" - let doubled = fun (x: Dynamic) -> Decode.int x |> Result.map (fun n -> n * 2) - - match Decode.list doubled d with - | Ok arr -> - Array.length arr |> equal 3 - arr.[0] |> equal 2 - arr.[2] |> equal 6 - | Error e -> failwithf "expected Ok, got Error %s" e -#else - () -#endif + +let tests = + testList ( + "Dynamic", + [ test ("Decode.int succeeds on integer", fun _ -> + let d: Dynamic = emitErlExpr () "42" + match Decode.int d with + | Ok v -> assertThat v (isEqualTo 42) + | Error e -> failwithf "expected Ok, got Error %s" e + ) + + test ("Decode.int fails on non-integer", fun _ -> + let d: Dynamic = emitErlExpr () "<<\"hello\">>" + match Decode.int d with + | Ok _ -> failwith "expected Error" + | Error _ -> assertThat true (isTrue) + ) + + test ("Decode.string succeeds on binary", fun _ -> + let d: Dynamic = emitErlExpr () "<<\"hello\">>" + match Decode.string d with + | Ok v -> assertThat v (isEqualTo "hello") + | Error e -> failwithf "expected Ok, got Error %s" e + ) + + test ("Decode.atom succeeds on atom", fun _ -> + let d: Dynamic = emitErlExpr () "ok" + match Decode.atom d with + // don't compare Atom values across the boundary, just ensure decode succeeded + | Ok _ -> assertThat true (isTrue) + | Error e -> failwithf "expected Ok, got Error %s" e + ) + + test ("Decode.bool succeeds on true", fun _ -> + let d: Dynamic = emitErlExpr () "true" + match Decode.bool d with + | Ok v -> assertThat v (isEqualTo true) + | Error e -> failwithf "expected Ok, got Error %s" e + ) + + test ("Decode.field extracts map value", fun _ -> + let d: Dynamic = emitErlExpr () "#{name => <<\"alice\">>, age => 30}" + let nameKey = Erlang.binaryToAtom "name" + let ageKey = Erlang.binaryToAtom "age" + + match Decode.field nameKey Decode.string d with + | Ok name -> assertThat name (isEqualTo "alice") + | Error e -> failwithf "expected Ok name, got Error %s" e + + match Decode.field ageKey Decode.int d with + | Ok age -> assertThat age (isEqualTo 30) + | Error e -> failwithf "expected Ok age, got Error %s" e + ) + + test ("Decode.field errors on missing key", fun _ -> + let d: Dynamic = emitErlExpr () "#{name => <<\"alice\">>}" + let missingKey = Erlang.binaryToAtom "nonexistent" + + match Decode.field missingKey Decode.string d with + | Ok _ -> failwith "expected Error on missing field" + | Error _ -> assertThat true (isTrue) + ) + + test ("Decode.list decodes homogeneous list", fun _ -> + let d: Dynamic = emitErlExpr () "[1, 2, 3, 4]" + match Decode.list Decode.int d with + | Ok arr -> + assertThat (Array.length arr) (isEqualTo 4) + assertThat arr.[0] (isEqualTo 1) + assertThat arr.[3] (isEqualTo 4) + | Error e -> failwithf "expected Ok, got Error %s" e + ) + + test ("Decode.list short-circuits on first decode error", fun _ -> + let d: Dynamic = emitErlExpr () "[1, 2, <<\"not_int\">>, 4]" + match Decode.list Decode.int d with + | Ok _ -> failwith "expected Error" + | Error _ -> assertThat true (isTrue) + ) + + test ("Decode.optional returns None for undefined", fun _ -> + let d: Dynamic = emitErlExpr () "undefined" + match Decode.optional Decode.int d with + | Ok None -> assertThat true (isTrue) + | Ok(Some v) -> failwithf "expected None, got Some %d" v + | Error e -> failwithf "expected Ok None, got Error %s" e + ) + + test ("Decode.optional returns Some for value", fun _ -> + let d: Dynamic = emitErlExpr () "42" + match Decode.optional Decode.int d with + | Ok(Some v) -> assertThat v (isEqualTo 42) + | Ok None -> failwith "expected Some" + | Error e -> failwithf "expected Ok, got Error %s" e + ) + + test ("Decode.tuple2 decodes a pair", fun _ -> + let d: Dynamic = emitErlExpr () "{<<\"alice\">>, 30}" + match Decode.tuple2 Decode.string Decode.int d with + | Ok (name, age) -> + assertThat name (isEqualTo "alice") + assertThat age (isEqualTo 30) + | Error e -> failwithf "expected Ok, got Error %s" e + ) + + test ("Decode.field with Atom.ofString key finds an atom-keyed field", fun _ -> + // Regression for the documented decoder example: the key must be a real atom. + // A binary key (what `Atom "name"` used to produce) never matches #{name => ...}. + let d: Dynamic = emitErlExpr () "#{name => <<\"alice\">>, age => 30}" + + match Decode.field (Atom.ofString "name") Decode.string d with + | Ok name -> assertThat name (isEqualTo "alice") + | Error e -> failwithf "expected Ok name, got Error %s" e + ) + + test ("Decode combinators accept a plain lambda decoder", fun _ -> + // No System.Func wrapper: a single-argument F# function compiles to the + // 1-arity Erlang fun the Emit applies. + let d: Dynamic = emitErlExpr () "[1, 2, 3]" + let doubled = fun (x: Dynamic) -> Decode.int x |> Result.map (fun n -> n * 2) + + match Decode.list doubled d with + | Ok arr -> + assertThat (Array.length arr) (isEqualTo 3) + assertThat arr.[0] (isEqualTo 2) + assertThat arr.[2] (isEqualTo 6) + | Error e -> failwithf "expected Ok, got Error %s" e + ) ] + ) diff --git a/test/TestErlang.fs b/test/TestErlang.fs index a18125d..481010a 100644 --- a/test/TestErlang.fs +++ b/test/TestErlang.fs @@ -1,8 +1,9 @@ module Fable.Beam.Tests.Erlang -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam @@ -11,409 +12,256 @@ open Fable.Beam.Lists type RecvMsg = | [] Ping | [] Data of value: int -#endif - -[] -let ``test self returns a pid`` () = -#if FABLE_COMPILER - let pid = Erlang.self () - let isAlive = Erlang.isProcessAlive pid - isAlive |> equal true -#else - () -#endif - -[] -let ``test makeRef returns unique references`` () = -#if FABLE_COMPILER - let ref1 = Erlang.makeRef () - let ref2 = Erlang.makeRef () - Erlang.exactEquals ref1 ref2 |> equal false -#else - () -#endif - -[] -let ``test exactEquals on same ref`` () = -#if FABLE_COMPILER - let ref1 = Erlang.makeRef () - Erlang.exactEquals ref1 ref1 |> equal true -#else - () -#endif - -[] -let ``test spawn creates a process`` () = -#if FABLE_COMPILER - let pid = Erlang.spawn (fun () -> ()) - Erlang.isProcessAlive pid |> equal true -#else - () -#endif - -[] -let ``test spawnLink creates a linked process`` () = -#if FABLE_COMPILER - let pid = Erlang.spawnLink (fun () -> ()) - Erlang.isProcessAlive pid |> equal true -#else - () -#endif - -[] -let ``test isProcessAlive on self`` () = -#if FABLE_COMPILER - let pid = Erlang.self () - Erlang.isProcessAlive pid |> equal true -#else - () -#endif - -[] -let ``test process dictionary get/put/erase`` () = -#if FABLE_COMPILER - let key = Erlang.makeRef () - Erlang.put key (box 42) |> ignore - - match Erlang.get key with - | Some v -> v |> equal (box 42) - | None -> failwith "process dict key should be set" - - Erlang.erase key |> ignore -#else - () -#endif - -[] -let ``test send and receive`` () = -#if FABLE_COMPILER - // A nullary DU case compiles to a bare atom (`ping`), not a 1-tuple (`{ping}`) -- only a - // case *with* fields becomes a tagged tuple, e.g. `Data 42` -> `{data, 42}`. Sending - // `{ping}` here never matched the generated receive clause, so this test used to sit out - // the full 1000ms timeout and take the None branch. - emitErlExpr () "erlang:self() ! ping" - - match Erlang.receive 1000 with - | Some Ping -> equal 1 1 - | _ -> equal 0 1 -#else - () -#endif - -[] -let ``test receive with timeout returns None`` () = -#if FABLE_COMPILER - match Erlang.receive 0 with - | None -> equal 1 1 - | Some _ -> equal 0 1 -#else - () -#endif - -[] -let ``test receive with data`` () = -#if FABLE_COMPILER - emitErlExpr () "erlang:self() ! {data, 42}" - - match Erlang.receive 1000 with - | Some(Data v) -> equal 42 v - | _ -> equal 0 1 -#else - () -#endif - -[] -let ``test sendAfter and cancelTimer`` () = -#if FABLE_COMPILER - let timerRef = Erlang.sendAfter 60000 (box "should_not_arrive") - - match Erlang.cancelTimer timerRef with - | Some remaining -> (remaining > 0) |> equal true - | None -> equal "Some" "None" -#else - () -#endif - -[] -let ``test atomToBinary and binaryToAtom roundtrip`` () = -#if FABLE_COMPILER - let atom = Erlang.binaryToAtom "test_atom" - let str = Erlang.atomToBinary atom - str |> equal "test_atom" -#else - () -#endif - -[] -let ``test monitor and demonitor`` () = -#if FABLE_COMPILER - let pid = Erlang.spawn (fun () -> Fable.Beam.Timer.timer.sleep 60000) - let ref = Erlang.monitor pid - Erlang.demonitorFlush ref - Erlang.exitPid pid (box "kill") -#else - () -#endif - -[] -let ``test register and whereis`` () = -#if FABLE_COMPILER - let name = Erlang.binaryToAtom "fable_beam_test_proc" - let pid = Erlang.self () - Erlang.register name pid - - match Erlang.whereis name with - | Some found -> Erlang.exactEquals pid found |> equal true - | None -> equal "Some" "None" -#else - () -#endif - -[] -let ``test date returns valid year month day`` () = -#if FABLE_COMPILER - let (year, month, day) = Erlang.date () - (year >= 2025) |> equal true - (month >= 1 && month <= 12) |> equal true - (day >= 1 && day <= 31) |> equal true -#else - () -#endif - -[] -let ``test dateYear dateMonth dateDay match date`` () = -#if FABLE_COMPILER - let (year, month, day) = Erlang.date () - Erlang.dateYear () |> equal year - Erlang.dateMonth () |> equal month - Erlang.dateDay () |> equal day -#else - () -#endif - -[] -let ``test time returns valid hour minute second`` () = -#if FABLE_COMPILER - let (hour, minute, second) = Erlang.time () - (hour >= 0 && hour <= 23) |> equal true - (minute >= 0 && minute <= 59) |> equal true - (second >= 0 && second <= 59) |> equal true -#else - () -#endif - -[] -let ``test localtime returns valid date and time`` () = -#if FABLE_COMPILER - let ((year, month, day), (hour, minute, second)) = Erlang.localtime () - (year >= 2025) |> equal true - (month >= 1 && month <= 12) |> equal true - (day >= 1 && day <= 31) |> equal true - (hour >= 0 && hour <= 23) |> equal true - (minute >= 0 && minute <= 59) |> equal true - (second >= 0 && second <= 59) |> equal true -#else - () -#endif - -[] -let ``test universaltime returns valid date and time`` () = -#if FABLE_COMPILER - let ((year, month, day), (hour, minute, second)) = Erlang.universaltime () - (year >= 2025) |> equal true - (month >= 1 && month <= 12) |> equal true - (day >= 1 && day <= 31) |> equal true - (hour >= 0 && hour <= 23) |> equal true - (minute >= 0 && minute <= 59) |> equal true - (second >= 0 && second <= 59) |> equal true -#else - () -#endif - -[] -let ``test monotonicTimeMs returns positive`` () = -#if FABLE_COMPILER - let t1 = Erlang.monotonicTimeMs () - let t2 = Erlang.monotonicTimeMs () - (t2 >= t1) |> equal true -#else - () -#endif - -[] -let ``test whereis returns None for unregistered name`` () = -#if FABLE_COMPILER - let name = Erlang.binaryToAtom "fable_beam_nonexistent_12345" - Erlang.whereis name |> equal None -#else - () -#endif - -[] -let ``test trapExit returns old value`` () = -#if FABLE_COMPILER - let old1 = Erlang.trapExit () - // Second call should return true since we just set it - let old2 = Erlang.trapExit () - old2 |> equal true - // Reset: set trap_exit back to false - Erlang.processFlag (Erlang.binaryToAtom "trap_exit") (box false) |> ignore -#else - () -#endif - -[] -let ``test cancelTimer returns None for invalid ref`` () = -#if FABLE_COMPILER - let fakeRef = Erlang.makeRef () - // cancelTimer on a non-timer ref returns None (false in Erlang) - // Note: makeRef() does not create a timer ref, but we can test - // that sendAfter + cancel works and returns Some - let timerRef = Erlang.sendAfter 60000 (box "test") - - match Erlang.cancelTimer timerRef with - | Some ms -> (ms >= 0) |> equal true - | None -> equal "Some" "None" - // Cancelling again should return None - Erlang.cancelTimer timerRef |> equal None -#else - () -#endif - -[] -let ``test sendAfterTo sends to specific pid`` () = -#if FABLE_COMPILER - let pid = Erlang.self () - let timerRef = Erlang.sendAfterTo 60000 pid (box "msg") - - match Erlang.cancelTimer timerRef with - | Some _ -> equal true true - | None -> equal "Some" "None" -#else - () -#endif - -[] -let ``test byteSize returns correct size`` () = -#if FABLE_COMPILER - Erlang.byteSize "hello" |> equal 5 - Erlang.byteSize "" |> equal 0 - Erlang.byteSize "abc" |> equal 3 -#else - () -#endif - -[] -let ``test atomToList returns charlist not binary`` () = -#if FABLE_COMPILER - let atom = Erlang.binaryToAtom "test" - let charlist = Erlang.atomToList atom - // atomToList returns a charlist (Erlang list of integers), - // which is not the same as an F# string (binary). - // We verify by round-tripping through listToAtom. - let atom2 = Erlang.listToAtom charlist - Erlang.atomToBinary atom2 |> equal "test" -#else - () -#endif - -[] -let ``test binaryToList returns list of bytes`` () = -#if FABLE_COMPILER - let bytes = Erlang.binaryToList "ABC" - Erlang.length bytes |> equal 3 - Erlang.head bytes |> equal 65 -#else - () -#endif - -[] -let ``test binaryToList and listToBinary roundtrip`` () = -#if FABLE_COMPILER - let original = "hello" - let bytes = Erlang.binaryToList original - Erlang.listToBinary bytes |> equal original -#else - () -#endif - - -[] -let ``test isEmpty returns true for empty list`` () = -#if FABLE_COMPILER - let empty: BeamList = emitErlExpr () "[]" - Erlang.isEmpty empty |> equal true -#else - () -#endif - -[] -let ``test isEmpty returns false for non-empty list`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - Erlang.isEmpty xs |> equal false -#else - () -#endif - -[] -let ``test head returns first element`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[42, 2, 3]" - Erlang.head xs |> equal 42 -#else - () -#endif - -[] -let ``test head preserves element type with tuples`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[{1, <<\"a\">>}, {2, <<\"b\">>}]" - let (n, s) = Erlang.head xs - n |> equal 1 - s |> equal "a" -#else - () -#endif - -[] -let ``test tail returns rest of list`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - let tl = Erlang.tail xs - Erlang.head tl |> equal 2 - Erlang.isEmpty (Erlang.tail tl |> Erlang.tail) |> equal true -#else - () -#endif - -[] -let ``test head raises on empty list`` () = -#if FABLE_COMPILER - let empty: BeamList = emitErlExpr () "[]" - throwsAnyError (fun () -> Erlang.head empty) -#else - () -#endif - -[] -let ``test tail raises on empty list`` () = -#if FABLE_COMPILER - let empty: BeamList = emitErlExpr () "[]" - throwsAnyError (fun () -> Erlang.tail empty) -#else - () -#endif - -[] -let ``test Atom.ofString builds a real atom, not a binary`` () = -#if FABLE_COMPILER - // Regression: the erased `Atom` constructor used to be public, so `Atom "x"` - // compiled to the binary <<"x">> and silently failed to match atom-keyed terms. - let a = Atom.ofString "test_real_atom" - let isAtom: bool = emitErlExpr a "erlang:is_atom($0)" - isAtom |> equal true - Atom.toString a |> equal "test_real_atom" -#else - () -#endif + +let tests = + testList ( + "Erlang", + [ test ("self returns a pid", fun _ -> + let pid = Erlang.self () + let isAlive = Erlang.isProcessAlive pid + assertThat isAlive (isTrue) + ) + + test ("makeRef returns unique references", fun _ -> + let ref1 = Erlang.makeRef () + let ref2 = Erlang.makeRef () + assertThat (Erlang.exactEquals ref1 ref2) (isFalse)) + + test ("exactEquals on same ref", fun _ -> + let ref1 = Erlang.makeRef () + assertThat (Erlang.exactEquals ref1 ref1) (isTrue)) + + test ("spawn creates a process", fun _ -> + let pid = Erlang.spawn (fun () -> ()) + assertThat (Erlang.isProcessAlive pid) (isTrue)) + + test ("spawnLink creates a linked process", fun _ -> + let pid = Erlang.spawnLink (fun () -> ()) + assertThat (Erlang.isProcessAlive pid) (isTrue)) + + test ("isProcessAlive on self", fun _ -> + let pid = Erlang.self () + assertThat (Erlang.isProcessAlive pid) (isTrue)) + + test ("process dictionary get/put/erase", fun _ -> + let key = Erlang.makeRef () + Erlang.put key (box 42) |> ignore + + match Erlang.get key with + | Some v -> assertThat v (isEqualTo (box 42)) + | None -> failwith "process dict key should be set" + + Erlang.erase key |> ignore + ) + + test ("send and receive", fun _ -> + // A nullary DU case compiles to a bare atom (`ping`), not a 1-tuple (`{ping}`) -- only a + // case *with* fields becomes a tagged tuple, e.g. `Data 42` -> `{data, 42}`. Sending + // `{ping}` here never matched the generated receive clause, so this test used to sit out + // the full 1000ms timeout and take the None branch. + emitErlExpr () "erlang:self() ! ping" + + match Erlang.receive 1000 with + | Some Ping -> assertThat true (isTrue) + | _ -> failwith "expected to receive the ping message" + ) + + test ("receive with timeout returns None", fun _ -> + match Erlang.receive 0 with + | None -> assertThat true (isTrue) + | Some _ -> failwith "expected a timeout" + ) + + test ("receive with data", fun _ -> + emitErlExpr () "erlang:self() ! {data, 42}" + + match Erlang.receive 1000 with + | Some (Data v) -> assertThat v (isEqualTo 42) + | _ -> failwith "expected to receive the data message" + ) + + test ("sendAfter and cancelTimer", fun _ -> + let timerRef = Erlang.sendAfter 60000 (box "should_not_arrive") + + match Erlang.cancelTimer timerRef with + | Some remaining -> assertThat (remaining > 0) (isTrue) + | None -> failwith "expected cancelTimer to return the remaining time" + ) + + test ("atomToBinary and binaryToAtom roundtrip", fun _ -> + let atom = Erlang.binaryToAtom "test_atom" + let str = Erlang.atomToBinary atom + assertThat str (isEqualTo "test_atom") + ) + + test ("monitor and demonitor", fun _ -> + let pid = Erlang.spawn (fun () -> Fable.Beam.Timer.timer.sleep 60000) + let ref = Erlang.monitor pid + Erlang.demonitorFlush ref + Erlang.exitPid pid (box "kill") + ) + + test ("register and whereis", fun _ -> + let name = Erlang.binaryToAtom "fable_beam_test_proc" + let pid = Erlang.self () + Erlang.register name pid + + match Erlang.whereis name with + | Some found -> assertThat (Erlang.exactEquals pid found) (isTrue) + | None -> failwith "whereis should find the registered process" + ) + + test ("date returns valid year month day", fun _ -> + let (year, month, day) = Erlang.date () + assertThat (year >= 2025) (isTrue) + assertThat (month >= 1 && month <= 12) (isTrue) + assertThat (day >= 1 && day <= 31) (isTrue) + ) + + test ("dateYear dateMonth dateDay match date", fun _ -> + let (year, month, day) = Erlang.date () + assertThat (Erlang.dateYear ()) (isEqualTo year) + assertThat (Erlang.dateMonth ()) (isEqualTo month) + assertThat (Erlang.dateDay ()) (isEqualTo day) + ) + + test ("time returns valid hour minute second", fun _ -> + let (hour, minute, second) = Erlang.time () + assertThat (hour >= 0 && hour <= 23) (isTrue) + assertThat (minute >= 0 && minute <= 59) (isTrue) + assertThat (second >= 0 && second <= 59) (isTrue) + ) + + test ("localtime returns valid date and time", fun _ -> + let ((year, month, day), (hour, minute, second)) = Erlang.localtime () + assertThat (year >= 2025) (isTrue) + assertThat (month >= 1 && month <= 12) (isTrue) + assertThat (day >= 1 && day <= 31) (isTrue) + assertThat (hour >= 0 && hour <= 23) (isTrue) + assertThat (minute >= 0 && minute <= 59) (isTrue) + assertThat (second >= 0 && second <= 59) (isTrue) + ) + + test ("universaltime returns valid date and time", fun _ -> + let ((year, month, day), (hour, minute, second)) = Erlang.universaltime () + assertThat (year >= 2025) (isTrue) + assertThat (month >= 1 && month <= 12) (isTrue) + assertThat (day >= 1 && day <= 31) (isTrue) + assertThat (hour >= 0 && hour <= 23) (isTrue) + assertThat (minute >= 0 && minute <= 59) (isTrue) + assertThat (second >= 0 && second <= 59) (isTrue) + ) + + test ("monotonicTimeMs returns positive", fun _ -> + let t1 = Erlang.monotonicTimeMs () + let t2 = Erlang.monotonicTimeMs () + assertThat (t2 >= t1) (isTrue) + ) + + test ("whereis returns None for unregistered name", fun _ -> + let name = Erlang.binaryToAtom "fable_beam_nonexistent_12345" + assertThat (Erlang.whereis name) (isEqualTo None)) + + test ("trapExit returns old value", fun _ -> + let old1 = Erlang.trapExit () + // Second call should return true since we just set it + let old2 = Erlang.trapExit () + assertThat old2 (isTrue) + // Reset: set trap_exit back to false + Erlang.processFlag (Erlang.binaryToAtom "trap_exit") (box false) |> ignore + ) + + test ("cancelTimer returns None for invalid ref", fun _ -> + let fakeRef = Erlang.makeRef () + // cancelTimer on a non-timer ref returns None (false in Erlang). + let timerRef = Erlang.sendAfter 60000 (box "test") + + match Erlang.cancelTimer timerRef with + | Some ms -> assertThat (ms >= 0) (isTrue) + | None -> failwith "expected cancelTimer to return the remaining time" + // Cancelling again should return None + assertThat (Erlang.cancelTimer timerRef) (isEqualTo None) + ) + + test ("sendAfterTo sends to specific pid", fun _ -> + let pid = Erlang.self () + let timerRef = Erlang.sendAfterTo 60000 pid (box "msg") + + match Erlang.cancelTimer timerRef with + | Some _ -> assertThat true (isTrue) + | None -> failwith "expected cancelTimer to succeed" + ) + + test ("byteSize returns correct size", fun _ -> + assertThat (Erlang.byteSize "hello") (isEqualTo 5) + assertThat (Erlang.byteSize "") (isEqualTo 0) + assertThat (Erlang.byteSize "abc") (isEqualTo 3) + ) + + test ("atomToList returns charlist not binary", fun _ -> + let atom = Erlang.binaryToAtom "test" + let charlist = Erlang.atomToList atom + // atomToList returns a charlist (Erlang list of integers), + // which is not the same as an F# string (binary). + // We verify by round-tripping through listToAtom. + let atom2 = Erlang.listToAtom charlist + assertThat (Erlang.atomToBinary atom2) (isEqualTo "test") + ) + + test ("binaryToList returns list of bytes", fun _ -> + let bytes = Erlang.binaryToList "ABC" + assertThat (Erlang.length bytes) (isEqualTo 3) + assertThat (Erlang.head bytes) (isEqualTo 65) + ) + + test ("binaryToList and listToBinary roundtrip", fun _ -> + let original = "hello" + let bytes = Erlang.binaryToList original + assertThat (Erlang.listToBinary bytes) (isEqualTo original) + ) + + test ("isEmpty returns true for empty list", fun _ -> + let empty: BeamList = emitErlExpr () "[]" + assertThat (Erlang.isEmpty empty) (isTrue)) + + test ("isEmpty returns false for non-empty list", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (Erlang.isEmpty xs) (isFalse)) + + test ("head returns first element", fun _ -> + let xs: BeamList = emitErlExpr () "[42, 2, 3]" + assertThat (Erlang.head xs) (isEqualTo 42)) + + test ("head preserves element type with tuples", fun _ -> + let xs: BeamList = emitErlExpr () "[{1, <<\"a\">>}, {2, <<\"b\">>}]" + let (n, s) = Erlang.head xs + assertThat n (isEqualTo 1) + assertThat s (isEqualTo "a") + ) + + test ("tail returns rest of list", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + let tl = Erlang.tail xs + assertThat (Erlang.head tl) (isEqualTo 2) + assertThat (Erlang.isEmpty (Erlang.tail tl |> Erlang.tail)) (isTrue) + ) + + test ("head raises on empty list", fun _ -> + let empty: BeamList = emitErlExpr () "[]" + assertThat (fun () -> Erlang.head empty |> ignore) throws + ) + + test ("tail raises on empty list", fun _ -> + let empty: BeamList = emitErlExpr () "[]" + assertThat (fun () -> Erlang.tail empty |> ignore) throws + ) + + test ("Atom.ofString builds a real atom, not a binary", fun _ -> + // Regression: the erased `Atom` constructor used to be public, so `Atom "x"` + // compiled to the binary <<"x">> and silently failed to match atom-keyed terms. + let a = Atom.ofString "test_real_atom" + let isAtom: bool = emitErlExpr a "erlang:is_atom($0)" + assertThat isAtom (isTrue) + assertThat (Atom.toString a) (isEqualTo "test_real_atom") + ) ] + ) diff --git a/test/TestEts.fs b/test/TestEts.fs index 9af4d1e..ff566ce 100644 --- a/test/TestEts.fs +++ b/test/TestEts.fs @@ -1,84 +1,68 @@ module Fable.Beam.Tests.Ets -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.Ets open Fable.Beam -#endif -[] -let ``test ets create and delete`` () = -#if FABLE_COMPILER - let table = - ets.new_ (Erlang.binaryToAtom "test_table", [ Erlang.binaryToAtom "set" ]) +let tests = + testList ( + "Ets", + [ test ("ets create and delete", fun _ -> + let table = + ets.new_ (Erlang.binaryToAtom "test_table", [ Erlang.binaryToAtom "set" ]) - ets.delete table -#else - () -#endif + ets.delete table + ) -[] -let ``test ets insert and lookup`` () = -#if FABLE_COMPILER - let table = - ets.new_ (Erlang.binaryToAtom "lookup_table", [ Erlang.binaryToAtom "set" ]) + test ("ets insert and lookup", fun _ -> + let table = + ets.new_ (Erlang.binaryToAtom "lookup_table", [ Erlang.binaryToAtom "set" ]) - let tuple: obj = emitErlExpr () "{1, <<\"hello\">>}" - ets.insert (table, tuple) |> equal true - let result = ets.lookup (table, box 1) - Array.length result |> equal 1 - ets.delete table -#else - () -#endif + let tuple: obj = emitErlExpr () "{1, <<\"hello\">>}" + assertThat (ets.insert (table, tuple)) (isTrue) + let result = ets.lookup (table, box 1) + assertThat (Array.length result) (isEqualTo 1) + ets.delete table + ) -[] -let ``test ets tab2list`` () = -#if FABLE_COMPILER - let table = - ets.new_ (Erlang.binaryToAtom "list_table", [ Erlang.binaryToAtom "set" ]) + test ("ets tab2list", fun _ -> + let table = + ets.new_ (Erlang.binaryToAtom "list_table", [ Erlang.binaryToAtom "set" ]) - let t1: obj = emitErlExpr () "{1, <<\"a\">>}" - let t2: obj = emitErlExpr () "{2, <<\"b\">>}" - ets.insert (table, t1) |> ignore - ets.insert (table, t2) |> ignore - let all = ets.tab2list table - Array.length all |> equal 2 - ets.delete table -#else - () -#endif + let t1: obj = emitErlExpr () "{1, <<\"a\">>}" + let t2: obj = emitErlExpr () "{2, <<\"b\">>}" + ets.insert (table, t1) |> ignore + ets.insert (table, t2) |> ignore + let all = ets.tab2list table + assertThat (Array.length all) (isEqualTo 2) + ets.delete table + ) -[] -let ``test ets typed info accessors`` () = -#if FABLE_COMPILER - let table = - ets.new_ (Erlang.binaryToAtom "info_table", [ Erlang.binaryToAtom "set" ]) + test ("ets typed info accessors", fun _ -> + let table = + ets.new_ (Erlang.binaryToAtom "info_table", [ Erlang.binaryToAtom "set" ]) - let tuple: obj = emitErlExpr () "{1, <<\"hello\">>}" - ets.insert (table, tuple) |> ignore + let tuple: obj = emitErlExpr () "{1, <<\"hello\">>}" + ets.insert (table, tuple) |> ignore - size table |> equal 1 - tableType table |> equal Set - access table |> equal Protected // default access - keypos table |> equal 1 // default keypos + assertThat (size table) (isEqualTo 1) + assertThat (tableType table) (isEqualTo Set) + assertThat (access table) (isEqualTo Protected) // default access + assertThat (keypos table) (isEqualTo 1) // default keypos - ets.delete table -#else - () -#endif + ets.delete table + ) -[] -let ``test ets typed info with ordered_set CompiledName`` () = -#if FABLE_COMPILER - let table = - ets.new_ (Erlang.binaryToAtom "ordered_table", [ Erlang.binaryToAtom "ordered_set" ]) + test ("ets typed info with ordered_set CompiledName", fun _ -> + let table = + ets.new_ (Erlang.binaryToAtom "ordered_table", [ Erlang.binaryToAtom "ordered_set" ]) - tableType table |> equal OrderedSet - ets.delete table -#else - () -#endif + assertThat (tableType table) (isEqualTo OrderedSet) + ets.delete table + ) ] + ) diff --git a/test/TestFile.fs b/test/TestFile.fs index b264c90..30f4ceb 100644 --- a/test/TestFile.fs +++ b/test/TestFile.fs @@ -1,140 +1,95 @@ module Fable.Beam.Tests.File -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Beam.File -#endif -// ============================================================================ -// Typed API -// ============================================================================ +let tests = + testList ( + "File", + [ test ("readFile and writeFile roundtrip", fun _ -> + let path = "/tmp/fable_beam_typed_test.txt" + let writeResult = writeFile path "typed hello" + assertThat writeResult (isEqualTo (Ok ())) + let readResult = readFile path + assertThat readResult (isEqualTo (Ok "typed hello")) + delete path |> ignore + ) -[] -let ``test readFile and writeFile roundtrip`` () = -#if FABLE_COMPILER - let path = "/tmp/fable_beam_typed_test.txt" - let writeResult = writeFile path "typed hello" - writeResult |> equal (Ok()) - let readResult = readFile path - readResult |> equal (Ok "typed hello") - delete path |> ignore -#else - () -#endif + test ("readFile returns Error for missing file", fun _ -> + let result = readFile "/tmp/fable_beam_nonexistent_file.txt" + assertThat result (isEqualTo (Error "enoent")) + ) -[] -let ``test readFile returns Error for missing file`` () = -#if FABLE_COMPILER - let result = readFile "/tmp/fable_beam_nonexistent_file.txt" - result |> equal (Error "enoent") -#else - () -#endif + test ("writeFile and delete roundtrip", fun _ -> + let path = "/tmp/fable_beam_delete_test.txt" + writeFile path "to delete" |> ignore + let delResult = delete path + assertThat delResult (isEqualTo (Ok ())) + let readResult = readFile path + assertThat readResult (isEqualTo (Error "enoent")) + ) -[] -let ``test writeFile and delete roundtrip`` () = -#if FABLE_COMPILER - let path = "/tmp/fable_beam_delete_test.txt" - writeFile path "to delete" |> ignore - let delResult = delete path - delResult |> equal (Ok()) - let readResult = readFile path - readResult |> equal (Error "enoent") -#else - () -#endif + test ("delete returns Error for missing file", fun _ -> + let result = delete "/tmp/fable_beam_nonexistent_delete.txt" + assertThat result (isEqualTo (Error "enoent")) + ) -[] -let ``test delete returns Error for missing file`` () = -#if FABLE_COMPILER - let result = delete "/tmp/fable_beam_nonexistent_delete.txt" - result |> equal (Error "enoent") -#else - () -#endif + test ("makeDir and delDir", fun _ -> + let path = "/tmp/fable_beam_test_dir" + let mkResult = makeDir path + assertThat mkResult (isEqualTo (Ok ())) + let delResult = delDir path + assertThat delResult (isEqualTo (Ok ())) + ) -[] -let ``test makeDir and delDir`` () = -#if FABLE_COMPILER - let path = "/tmp/fable_beam_test_dir" - let mkResult = makeDir path - mkResult |> equal (Ok()) - let delResult = delDir path - delResult |> equal (Ok()) -#else - () -#endif + test ("listDir returns files", fun _ -> + let dir = "/tmp/fable_beam_listdir_test" + makeDir dir |> ignore + writeFile (dir + "/a.txt") "a" |> ignore + writeFile (dir + "/b.txt") "b" |> ignore + let result = listDir dir -[] -let ``test listDir returns files`` () = -#if FABLE_COMPILER - let dir = "/tmp/fable_beam_listdir_test" - makeDir dir |> ignore - writeFile (dir + "/a.txt") "a" |> ignore - writeFile (dir + "/b.txt") "b" |> ignore - let result = listDir dir + match result with + | Ok files -> assertThat ((List.length files >= 2)) (isTrue) + | Error e -> failwith "ok" + // cleanup + delete (dir + "/a.txt") |> ignore + delete (dir + "/b.txt") |> ignore + delDir dir |> ignore + ) - match result with - | Ok files -> (List.length files >= 2) |> equal true - | Error e -> equal "ok" e - // cleanup - delete (dir + "/a.txt") |> ignore - delete (dir + "/b.txt") |> ignore - delDir dir |> ignore -#else - () -#endif + test ("listDir returns Error for missing dir", fun _ -> + let result = listDir "/tmp/fable_beam_no_such_dir" + assertThat result (isEqualTo (Error "enoent")) + ) -[] -let ``test listDir returns Error for missing dir`` () = -#if FABLE_COMPILER - let result = listDir "/tmp/fable_beam_no_such_dir" - result |> equal (Error "enoent") -#else - () -#endif + test ("rename moves a file", fun _ -> + let src = "/tmp/fable_beam_rename_src.txt" + let dst = "/tmp/fable_beam_rename_dst.txt" + writeFile src "rename me" |> ignore + let result = rename src dst + assertThat result (isEqualTo (Ok ())) + assertThat (readFile dst) (isEqualTo (Ok "rename me")) + assertThat (readFile src) (isEqualTo (Error "enoent")) + delete dst |> ignore + ) -[] -let ``test rename moves a file`` () = -#if FABLE_COMPILER - let src = "/tmp/fable_beam_rename_src.txt" - let dst = "/tmp/fable_beam_rename_dst.txt" - writeFile src "rename me" |> ignore - let result = rename src dst - result |> equal (Ok()) - readFile dst |> equal (Ok "rename me") - readFile src |> equal (Error "enoent") - delete dst |> ignore -#else - () -#endif + test ("getCwd returns a path", fun _ -> + match getCwd () with + | Ok dir -> assertThat ((String.length dir > 0)) (isTrue) + | Error e -> failwith "ok" + ) -[] -let ``test getCwd returns a path`` () = -#if FABLE_COMPILER - match getCwd () with - | Ok dir -> (String.length dir > 0) |> equal true - | Error e -> equal "ok" e -#else - () -#endif + test ("exists returns true for existing file", fun _ -> + let path = "/tmp/fable_beam_exists_test.txt" + writeFile path "exists" |> ignore + assertThat (exists path) (isTrue) + delete path |> ignore + ) -[] -let ``test exists returns true for existing file`` () = -#if FABLE_COMPILER - let path = "/tmp/fable_beam_exists_test.txt" - writeFile path "exists" |> ignore - exists path |> equal true - delete path |> ignore -#else - () -#endif - -[] -let ``test exists returns false for missing file`` () = -#if FABLE_COMPILER - exists "/tmp/fable_beam_no_such_file.txt" |> equal false -#else - () -#endif + test ("exists returns false for missing file", fun _ -> + assertThat (exists "/tmp/fable_beam_no_such_file.txt") (isFalse)) ] + ) diff --git a/test/TestGenServer.fs b/test/TestGenServer.fs index fa1a2a0..375d387 100644 --- a/test/TestGenServer.fs +++ b/test/TestGenServer.fs @@ -1,137 +1,125 @@ module Fable.Beam.Tests.GenServer -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.GenServer -#endif - -[] -let ``test gen_server.stop on non-existent catches error`` () = -#if FABLE_COMPILER - try - gen_server.stop (ServerRef "nonexistent_process_xyz") - with _ -> - () -#else - () -#endif - -[] -let ``test gen_server.start_link returns ok with pid`` () = -#if FABLE_COMPILER - let result = - gen_server.start_link (Erlang.binaryToAtom "test_counter_server", box 0, []) - - match result with - | Ok pid -> Erlang.isProcessAlive pid |> equal true - | Error _ -> failwith "start_link should succeed" -#else - () -#endif - -[] -let ``test gen_server.start returns ok with pid`` () = -#if FABLE_COMPILER - let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) - - match result with - | Ok pid -> - Erlang.isProcessAlive pid |> equal true - gen_server.stop (ServerRef pid) - | Error _ -> failwith "start should succeed" -#else - () -#endif - -[] -let ``test gen_server.call gets state`` () = -#if FABLE_COMPILER - let result = - gen_server.start (Erlang.binaryToAtom "test_counter_server", box 42, []) - - match result with - | Ok pid -> - let value = gen_server.call (ServerRef pid, box (Erlang.binaryToAtom "get")) - Erlang.exactEquals value (box 42) |> equal true - gen_server.stop (ServerRef pid) - | Error _ -> failwith "start should succeed" -#else - () -#endif - -[] -let ``test gen_server.call increment`` () = -#if FABLE_COMPILER - let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) - - match result with - | Ok pid -> - let ref = ServerRef pid - let v1 = gen_server.call (ref, box (Erlang.binaryToAtom "increment")) - Erlang.exactEquals v1 (box 1) |> equal true - let v2 = gen_server.call (ref, box (Erlang.binaryToAtom "increment")) - Erlang.exactEquals v2 (box 2) |> equal true - gen_server.stop ref - | Error _ -> failwith "start should succeed" -#else - () -#endif - -[] -let ``test gen_server.call with timeout`` () = -#if FABLE_COMPILER - let result = - gen_server.start (Erlang.binaryToAtom "test_counter_server", box 10, []) - - match result with - | Ok pid -> - let ref = ServerRef pid - let value = gen_server.call (ref, box (Erlang.binaryToAtom "get"), U2.Case1 5000) - Erlang.exactEquals value (box 10) |> equal true - gen_server.stop ref - | Error _ -> failwith "start should succeed" -#else - () -#endif - -[] -let ``test gen_server.cast updates state`` () = -#if FABLE_COMPILER - let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) - - match result with - | Ok pid -> - let ref = ServerRef pid - let setMsg: obj = emitErlExpr () "{set, 99}" - gen_server.cast (ref, setMsg) - // Small delay to let cast process - Fable.Beam.Timer.sleep 10 - let value = gen_server.call (ref, box (Erlang.binaryToAtom "get")) - Erlang.exactEquals value (box 99) |> equal true - gen_server.stop ref - | Error _ -> failwith "start should succeed" -#else - () -#endif - -[] -let ``test gen_server.stop with reason and timeout`` () = -#if FABLE_COMPILER - let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) - - match result with - | Ok pid -> - let ref = ServerRef pid - Erlang.isProcessAlive pid |> equal true - gen_server.stop (ref, Erlang.binaryToAtom "normal", U2.Case1 5000) - // Process should be dead after stop - Fable.Beam.Timer.sleep 10 - Erlang.isProcessAlive pid |> equal false - | Error _ -> failwith "start should succeed" -#else - () -#endif + +let tests = + testList ( + "GenServer", + [ test ( + "stop on non-existent catches error", + fun _ -> + try + gen_server.stop (ServerRef "nonexistent_process_xyz") + with _ -> + () + ) + + test ( + "start_link returns ok with pid", + fun _ -> + let result = + gen_server.start_link (Erlang.binaryToAtom "test_counter_server", box 0, []) + + match result with + | Ok pid -> assertThat (Erlang.isProcessAlive pid) (isEqualTo true) + | Error _ -> failwith "start_link should succeed" + ) + + test ( + "start returns ok with pid", + fun _ -> + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) + + match result with + | Ok pid -> + assertThat (Erlang.isProcessAlive pid) (isEqualTo true) + gen_server.stop (ServerRef pid) + | Error _ -> failwith "start should succeed" + ) + + test ( + "call gets state", + fun _ -> + let result = + gen_server.start (Erlang.binaryToAtom "test_counter_server", box 42, []) + + match result with + | Ok pid -> + let value = gen_server.call (ServerRef pid, box (Erlang.binaryToAtom "get")) + assertThat (Erlang.exactEquals value (box 42)) (isEqualTo true) + gen_server.stop (ServerRef pid) + | Error _ -> failwith "start should succeed" + ) + + test ( + "call increment", + fun _ -> + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) + + match result with + | Ok pid -> + let ref = ServerRef pid + let v1 = gen_server.call (ref, box (Erlang.binaryToAtom "increment")) + assertThat (Erlang.exactEquals v1 (box 1)) (isEqualTo true) + let v2 = gen_server.call (ref, box (Erlang.binaryToAtom "increment")) + assertThat (Erlang.exactEquals v2 (box 2)) (isEqualTo true) + gen_server.stop ref + | Error _ -> failwith "start should succeed" + ) + + test ( + "call with timeout", + fun _ -> + let result = + gen_server.start (Erlang.binaryToAtom "test_counter_server", box 10, []) + + match result with + | Ok pid -> + let ref = ServerRef pid + let value = gen_server.call (ref, box (Erlang.binaryToAtom "get"), U2.Case1 5000) + assertThat (Erlang.exactEquals value (box 10)) (isEqualTo true) + gen_server.stop ref + | Error _ -> failwith "start should succeed" + ) + + test ( + "cast updates state", + fun _ -> + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) + + match result with + | Ok pid -> + let ref = ServerRef pid + let setMsg: obj = emitErlExpr () "{set, 99}" + gen_server.cast (ref, setMsg) + // Small delay to let cast process + Fable.Beam.Timer.sleep 10 + let value = gen_server.call (ref, box (Erlang.binaryToAtom "get")) + assertThat (Erlang.exactEquals value (box 99)) (isEqualTo true) + gen_server.stop ref + | Error _ -> failwith "start should succeed" + ) + + test ( + "stop with reason and timeout", + fun _ -> + let result = gen_server.start (Erlang.binaryToAtom "test_counter_server", box 0, []) + + match result with + | Ok pid -> + let ref = ServerRef pid + assertThat (Erlang.isProcessAlive pid) (isEqualTo true) + gen_server.stop (ref, Erlang.binaryToAtom "normal", U2.Case1 5000) + // Process should be dead after stop + Fable.Beam.Timer.sleep 10 + assertThat (Erlang.isProcessAlive pid) (isEqualTo false) + | Error _ -> failwith "start should succeed" + ) ] + ) diff --git a/test/TestIo.fs b/test/TestIo.fs index 23150da..5d04e90 100644 --- a/test/TestIo.fs +++ b/test/TestIo.fs @@ -1,31 +1,20 @@ module Fable.Beam.Tests.Io -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Beam.Io -#endif -[] -let ``test io.put_chars works`` () = -#if FABLE_COMPILER - io.put_chars "test output\n" -#else - () -#endif +let tests = + testList ( + "Io", + [ test ("put_chars works", fun _ -> + assertThat (fun () -> io.put_chars "test output\n") doesNotThrow) -[] -let ``test putChars does not crash`` () = -#if FABLE_COMPILER - putChars "typed putChars test\n" -#else - () -#endif + test ("putChars does not crash", fun _ -> + assertThat (fun () -> putChars "typed putChars test\n") doesNotThrow) -[] -let ``test format does not crash`` () = -#if FABLE_COMPILER - format "hello ~s~n" [ box "beam" ] -#else - () -#endif + test ("format does not crash", fun _ -> + assertThat (fun () -> format "hello ~s~n" [ box "beam" ]) doesNotThrow) ] + ) diff --git a/test/TestIoLib.fs b/test/TestIoLib.fs index b50b2af..a26174e 100644 --- a/test/TestIoLib.fs +++ b/test/TestIoLib.fs @@ -1,8 +1,9 @@ module Fable.Beam.Tests.IoLib -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Beam open Fable.Beam.IoLib @@ -10,23 +11,19 @@ open Fable.Beam.IoLib // The raw variant returns unflattened chardata (a deep iolist), i.e. a list, not a binary. [] let private isList (x: BeamChardata) : bool = nativeOnly -#endif -[] -let ``test io_lib format renders a string`` () = -#if FABLE_COMPILER - format "~s-~p" [ box "x"; box 42 ] |> equal "x-42" -#else - () -#endif +let tests = + testList ( + "IoLib", + [ test ("format renders a string", fun _ -> + assertThat (format "~s-~p" [ box "x"; box 42 ]) (isEqualTo "x-42")) -[] -let ``test io_lib formatRaw returns unflattened chardata that flattens to format`` () = -#if FABLE_COMPILER - let raw = formatRaw "~s-~p" [ box "x"; box 42 ] - isList raw |> equal true - BeamChardata.toString raw |> equal "x-42" - BeamChardata.toString raw |> equal (format "~s-~p" [ box "x"; box 42 ]) -#else - () -#endif + test ( + "formatRaw returns unflattened chardata that flattens to format", + fun _ -> + let raw = formatRaw "~s-~p" [ box "x"; box 42 ] + assertThat (isList raw) (isTrue) + assertThat (BeamChardata.toString raw) (isEqualTo "x-42") + assertThat (BeamChardata.toString raw) (isEqualTo (format "~s-~p" [ box "x"; box 42 ])) + ) ] + ) diff --git a/test/TestJsx.fs b/test/TestJsx.fs index 3f5bd5d..ccb37ac 100644 --- a/test/TestJsx.fs +++ b/test/TestJsx.fs @@ -1,93 +1,14 @@ module Fable.Beam.Tests.Jsx -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core +open Fable.Core.BeamInterop open Fable.Beam.Jsx.Jsx -#endif - -[] -let ``test jsx encode integer`` () = -#if FABLE_COMPILER - let json = jsx.encode 42 - json |> equal "42" -#else - () -#endif - -[] -let ``test jsx encode string`` () = -#if FABLE_COMPILER - let json = jsx.encode "hello" - json |> equal "\"hello\"" -#else - () -#endif - -[] -let ``test jsx decode string`` () = -#if FABLE_COMPILER - let result: string = jsx.decode "\"hello\"" - result |> equal "hello" -#else - () -#endif - -[] -let ``test jsx is_json valid`` () = -#if FABLE_COMPILER - jsx.is_json """{"key": "value"}""" |> equal true -#else - () -#endif - -[] -let ``test jsx is_json invalid`` () = -#if FABLE_COMPILER - jsx.is_json "not json" |> equal false -#else - () -#endif - -[] -let ``test jsx minify`` () = -#if FABLE_COMPILER - let result = jsx.minify """{ "key" : "value" }""" - result |> equal """{"key":"value"}""" -#else - () -#endif - -[] -let ``test jsx prettify and minify roundtrip`` () = -#if FABLE_COMPILER - let json = """{"key":"value"}""" - let pretty = jsx.prettify json - let mini = jsx.minify pretty - mini |> equal json -#else - () -#endif - -[] -let ``test jsx is_json with strict rejects trailing comma`` () = -#if FABLE_COMPILER - jsx.is_json ("""{"key": "value",}""", [ strict ]) |> equal false -#else - () -#endif -[] -let ``test jsx format with indent`` () = #if FABLE_COMPILER - let result = jsx.format ("""{"key":"value"}""", [ indent 2 ]) - // Formatted output should be longer than minified - (String.length result > String.length """{"key":"value"}""") |> equal true -#else - () -#endif - // `labels` is a *decoder* option: jsx:is_json/2 does not accept it and simply answers `false`, // so is_json can never show that the option took effect. These tests decode instead and inspect // the key type of the resulting map -- if Fable stringified the DU case, jsx would receive @@ -97,58 +18,84 @@ let private firstKeyIsAtom (m: obj) : bool = nativeOnly [ is_binary(K); _ -> false end")>] let private firstKeyIsBinary (m: obj) : bool = nativeOnly - -[] -let ``test jsx labels Binary keeps keys as binaries`` () = -#if FABLE_COMPILER - let decoded: obj = jsx.decode ("""{"key":"value"}""", [ labels LabelMode.Binary ]) - - firstKeyIsBinary decoded |> equal true -#else - () -#endif - -[] -let ``test jsx labels Atom converts keys to atoms`` () = -#if FABLE_COMPILER - let decoded: obj = jsx.decode ("""{"key":"value"}""", [ labels LabelMode.Atom ]) - - firstKeyIsAtom decoded |> equal true -#else - () -#endif - -[] -let ``test jsx labels Atom probe emits raw atom option`` () = -#if FABLE_COMPILER - let decoded: obj = - jsx.decode ("""{"atom_key_probe_xyz":"value"}""", [ labels LabelMode.Atom ]) - - firstKeyIsAtom decoded |> equal true -#else - () #endif -[] -let ``test jsx labels ExistingAtom rejects unknown atoms`` () = -#if FABLE_COMPILER - // existing_atom uses binary_to_existing_atom, so a key whose atom was never created - // raises badarg rather than silently interning it. That rejection *is* the behaviour. - (fun () -> jsx.decode ("""{"never_interned_key_qqq":"value"}""", [ labels LabelMode.ExistingAtom ])) - |> throwsAnyError -#else - () -#endif - -[] -let ``test jsx labels AttemptAtom is accepted`` () = -#if FABLE_COMPILER - // attempt_atom converts the key when the atom already exists and leaves it a binary - // otherwise. "key" is interned by the decode above, so it comes back as an atom. - let decoded: obj = - jsx.decode ("""{"key":"value"}""", [ labels LabelMode.AttemptAtom ]) - - firstKeyIsAtom decoded |> equal true -#else - () -#endif +let tests = + testList ( + "Jsx", + [ test ("jsx encode integer", fun _ -> + let json = jsx.encode 42 + assertThat json (isEqualTo "42") + ) + + test ("jsx encode string", fun _ -> + let json = jsx.encode "hello" + assertThat json (isEqualTo "\"hello\"") + ) + + test ("jsx decode string", fun _ -> + let result: string = jsx.decode "\"hello\"" + assertThat result (isEqualTo "hello") + ) + + test ("jsx is_json valid", fun _ -> + assertThat (jsx.is_json """{"key": "value"}""") (isTrue)) + + test ("jsx is_json invalid", fun _ -> + assertThat (jsx.is_json "not json") (isFalse)) + + test ("jsx minify", fun _ -> + let result = jsx.minify """{ "key" : "value" }""" + assertThat result (isEqualTo """{"key":"value"}""") + ) + + test ("jsx prettify and minify roundtrip", fun _ -> + let json = """{"key":"value"}""" + let pretty = jsx.prettify json + let mini = jsx.minify pretty + assertThat mini (isEqualTo json) + ) + + test ("jsx is_json with strict rejects trailing comma", fun _ -> + assertThat (jsx.is_json ("""{"key": "value",}""", [ strict ])) (isFalse)) + + test ("jsx format with indent", fun _ -> + let result = jsx.format ("""{"key":"value"}""", [ indent 2 ]) + // Formatted output should be longer than minified + assertThat ((String.length result > String.length """{"key":"value"}""")) (isTrue) + ) + + test ("jsx labels Binary keeps keys as binaries", fun _ -> + let decoded: obj = jsx.decode ("""{"key":"value"}""", [ labels LabelMode.Binary ]) + + assertThat (firstKeyIsBinary decoded) (isTrue) + ) + + test ("jsx labels Atom converts keys to atoms", fun _ -> + let decoded: obj = jsx.decode ("""{"key":"value"}""", [ labels LabelMode.Atom ]) + + assertThat (firstKeyIsAtom decoded) (isTrue) + ) + + test ("jsx labels Atom probe emits raw atom option", fun _ -> + let decoded: obj = + jsx.decode ("""{"atom_key_probe_xyz":"value"}""", [ labels LabelMode.Atom ]) + + assertThat (firstKeyIsAtom decoded) (isTrue) + ) + + test ("jsx labels ExistingAtom rejects unknown atoms", fun _ -> + // existing_atom uses binary_to_existing_atom, so a key whose atom was never created + // raises badarg rather than silently interning it. That rejection *is* the behaviour. + assertThat (fun () -> jsx.decode ("""{"never_interned_key_qqq":"value"}""", [ labels LabelMode.ExistingAtom ]) |> ignore) throws + ) + + test ("jsx labels AttemptAtom is accepted", fun _ -> + // attempt_atom converts the key when the atom already exists and leaves it a binary + // otherwise. "key" is interned by the decode above, so it comes back as an atom. + let decoded: obj = + jsx.decode ("""{"key":"value"}""", [ labels LabelMode.AttemptAtom ]) + + assertThat (firstKeyIsAtom decoded) (isTrue) + ) ] + ) diff --git a/test/TestLists.fs b/test/TestLists.fs index 2834a09..0554c4c 100644 --- a/test/TestLists.fs +++ b/test/TestLists.fs @@ -1,391 +1,232 @@ module Fable.Beam.Tests.Lists -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.Lists [] let erlLength (xs: BeamList<'T>) : int = nativeOnly -#endif - -[] -let ``test lists.reverse works`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - let expected: BeamList = emitErlExpr () "[3, 2, 1]" - lists.reverse xs |> equal expected -#else - () -#endif - -[] -let ``test lists.member works`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - lists.``member`` (2, xs) |> equal true - lists.``member`` (4, xs) |> equal false -#else - () -#endif - -[] -let ``test lists.sort works`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[3, 1, 2]" - let expected: BeamList = emitErlExpr () "[1, 2, 3]" - lists.sort xs |> equal expected -#else - () -#endif - -[] -let ``test lists.append works`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2]" - let ys: BeamList = emitErlExpr () "[3, 4]" - let expected: BeamList = emitErlExpr () "[1, 2, 3, 4]" - lists.append (xs, ys) |> equal expected -#else - () -#endif - -[] -let ``test lists.last works`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - lists.last xs |> equal 3 -#else - () -#endif - -[] -let ``test lists.nth works`` () = -#if FABLE_COMPILER - // Erlang lists:nth is 1-based - let xs: BeamList = emitErlExpr () "[10, 20, 30]" - lists.nth (1, xs) |> equal 10 -#else - () -#endif - -[] -let ``test lists.flatten works`` () = -#if FABLE_COMPILER - let xs: BeamList> = emitErlExpr () "[[1, 2], [3, 4]]" - let expected: BeamList = emitErlExpr () "[1, 2, 3, 4]" - lists.flatten xs |> equal expected -#else - () -#endif - -[] -let ``test lists.usort removes duplicates`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[3, 1, 2, 1, 3]" - let expected: BeamList = emitErlExpr () "[1, 2, 3]" - lists.usort xs |> equal expected -#else - () -#endif - -[] -let ``test lists.unzip returns tuple of two lists`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[{1, a}, {2, b}, {3, c}]" - let (list1, list2) = lists.unzip xs - erlLength list1 |> equal 3 - erlLength list2 |> equal 3 -#else - () -#endif - -[] -let ``test lists.partition returns tuple of two lists`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" - - let (matching, notMatching) = lists.partition ((fun x -> x > 3), xs) - - erlLength matching |> equal 2 - erlLength notMatching |> equal 3 -#else - () -#endif - -[] -let ``test lists.sum returns sum`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 4]" - lists.sum xs |> equal 10 -#else - () -#endif - -[] -let ``test lists.sum returns float sum`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1.5, 2.5, 3.0]" - lists.sum xs |> equal 7.0 -#else - () -#endif - -[] -let ``test lists.max returns maximum`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[3, 1, 4, 1, 5, 9, 2]" - lists.max xs |> equal 9 -#else - () -#endif - -[] -let ``test lists.min returns minimum`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[3, 1, 4, 1, 5, 9, 2]" - lists.min xs |> equal 1 -#else - () -#endif - -[] -let ``test lists.seq generates integer sequence`` () = -#if FABLE_COMPILER - let xs = lists.seq (1, 5) - erlLength xs |> equal 5 - lists.nth (1, xs) |> equal 1 - lists.nth (5, xs) |> equal 5 -#else - () -#endif - -[] -let ``test lists.seq with step generates sequence`` () = -#if FABLE_COMPILER - let xs = lists.seq (0, 10, 2) - erlLength xs |> equal 6 - lists.nth (1, xs) |> equal 0 - lists.nth (2, xs) |> equal 2 -#else - () -#endif - -[] -let ``test lists.duplicate creates repeated list`` () = -#if FABLE_COMPILER - let xs: BeamList = lists.duplicate (3, 7) - erlLength xs |> equal 3 - lists.nth (1, xs) |> equal 7 - lists.nth (3, xs) |> equal 7 -#else - () -#endif - -[] -let ``test lists.takewhile takes while predicate holds`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" - let result = lists.takewhile ((fun x -> x < 4), xs) - erlLength result |> equal 3 - lists.nth (3, result) |> equal 3 -#else - () -#endif - -[] -let ``test lists.dropwhile drops while predicate holds`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" - let result = lists.dropwhile ((fun x -> x < 4), xs) - erlLength result |> equal 2 - lists.nth (1, result) |> equal 4 -#else - () -#endif - -[] -let ``test lists.splitwith splits at predicate boundary`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" - let (before, after) = lists.splitwith ((fun x -> x < 3), xs) - erlLength before |> equal 2 - erlLength after |> equal 3 -#else - () -#endif - -[] -let ``test lists.delete removes first occurrence`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 2, 1]" - let result = lists.delete (2, xs) - erlLength result |> equal 4 - lists.``member`` (2, result) |> equal true -#else - () -#endif - -[] -let ``test lists.subtract removes elements`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" - let ys: BeamList = emitErlExpr () "[2, 4]" - let result = lists.subtract (xs, ys) - erlLength result |> equal 3 - lists.``member`` (2, result) |> equal false -#else - () -#endif - -[] -let ``test lists.keysort sorts by Nth element`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[{3, c}, {1, a}, {2, b}]" - let sorted = lists.keysort (1, xs) - let first: int * string = lists.nth (1, sorted) - fst first |> equal 1 -#else - () -#endif - -[] -let ``test lists.keydelete removes first matching tuple`` () = -#if FABLE_COMPILER - let xs: BeamList = - emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}, {<<\"a\">>, 3}]" - - let result = lists.keydelete ("a", 1, xs) - erlLength result |> equal 2 -#else - () -#endif - -[] -let ``test lists.keymember checks for key`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}]" - lists.keymember ("a", 1, xs) |> equal true - lists.keymember ("c", 1, xs) |> equal false -#else - () -#endif - -[] -let ``test keyFind returns Some for existing key`` () = -#if FABLE_COMPILER - let xs: BeamList = - emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}, {<<\"c\">>, 3}]" - - keyFind "b" 1 xs |> equal (Some("b", 2)) -#else - () -#endif - -[] -let ``test keyFind returns None for missing key`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}]" - keyFind "z" 1 xs |> equal None -#else - () -#endif - -[] -let ``test lists.keyreplace replaces first matching tuple`` () = -#if FABLE_COMPILER - let xs: BeamList = - emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}, {<<\"a\">>, 3}]" - - let result = lists.keyreplace ("a", 1, xs, ("a", 99)) - lists.nth (1, result) |> equal ("a", 99) - erlLength result |> equal 3 -#else - () -#endif - -[] -let ``test lists.mapfoldl maps and folds left`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - - let (mapped, acc) = lists.mapfoldl ((fun x s -> (x * 2, s + x)), 0, xs) - - mapped |> equal (emitErlExpr () "[2, 4, 6]") - acc |> equal 6 -#else - () -#endif - -[] -let ``test lists.mapfoldr maps and folds right`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - - let (mapped, acc) = lists.mapfoldr ((fun x s -> (x * 2, s + x)), 0, xs) - - mapped |> equal (emitErlExpr () "[2, 4, 6]") - acc |> equal 6 -#else - () -#endif - -[] -let ``test lists.map applies a function to each element`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - lists.map ((fun x -> x * 2), xs) |> equal (emitErlExpr () "[2, 4, 6]") -#else - () -#endif - -[] -let ``test lists.filter keeps matching elements`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3, 4]" - lists.filter ((fun x -> x % 2 = 0), xs) |> equal (emitErlExpr () "[2, 4]") -#else - () -#endif - -[] -let ``test lists.foldl folds from the left`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - lists.foldl ((fun x acc -> acc + x), 0, xs) |> equal 6 -#else - () -#endif - -[] -let ``test lists.foldr folds from the right`` () = -#if FABLE_COMPILER - // Subtraction is not associative, so this pins the direction: 1-(2-(3-0)) = 2. - let xs: BeamList = emitErlExpr () "[1, 2, 3]" - lists.foldr ((fun x acc -> x - acc), 0, xs) |> equal 2 -#else - () -#endif - -[] -let ``test lists.all and lists.any check predicates`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[2, 4, 6]" - lists.all ((fun x -> x % 2 = 0), xs) |> equal true - lists.any ((fun x -> x > 5), xs) |> equal true - lists.any ((fun x -> x > 10), xs) |> equal false -#else - () -#endif - -[] -let ``test lists.sort with a comparison function`` () = -#if FABLE_COMPILER - let xs: BeamList = emitErlExpr () "[3, 1, 2]" - // Descending: the comparator returns true when A should come before B. - lists.sort ((fun a b -> a >= b), xs) |> equal (emitErlExpr () "[3, 2, 1]") -#else - () -#endif + +let tests = + testList ( + "Lists", + [ test ("reverse works", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + let expected: BeamList = emitErlExpr () "[3, 2, 1]" + assertThat (lists.reverse xs) (isEqualTo expected)) + + test ("member works", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (lists.``member`` (2, xs)) (isTrue) + assertThat (lists.``member`` (4, xs)) (isFalse) + ) + + test ("sort works", fun _ -> + let xs: BeamList = emitErlExpr () "[3, 1, 2]" + let expected: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (lists.sort xs) (isEqualTo expected)) + + test ("append works", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2]" + let ys: BeamList = emitErlExpr () "[3, 4]" + let expected: BeamList = emitErlExpr () "[1, 2, 3, 4]" + assertThat (lists.append (xs, ys)) (isEqualTo expected)) + + test ("last works", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (lists.last xs) (isEqualTo 3)) + + test ("nth works", fun _ -> + // Erlang lists:nth is 1-based + let xs: BeamList = emitErlExpr () "[10, 20, 30]" + assertThat (lists.nth (1, xs)) (isEqualTo 10)) + + test ("flatten works", fun _ -> + let xs: BeamList> = emitErlExpr () "[[1, 2], [3, 4]]" + let expected: BeamList = emitErlExpr () "[1, 2, 3, 4]" + assertThat (lists.flatten xs) (isEqualTo expected)) + + test ("usort removes duplicates", fun _ -> + let xs: BeamList = emitErlExpr () "[3, 1, 2, 1, 3]" + let expected: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (lists.usort xs) (isEqualTo expected)) + + test ("unzip returns tuple of two lists", fun _ -> + let xs: BeamList = emitErlExpr () "[{1, a}, {2, b}, {3, c}]" + let (list1, list2) = lists.unzip xs + assertThat (erlLength list1) (isEqualTo 3) + assertThat (erlLength list2) (isEqualTo 3) + ) + + test ("partition returns tuple of two lists", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" + + let (matching, notMatching) = lists.partition ((fun x -> x > 3), xs) + + assertThat (erlLength matching) (isEqualTo 2) + assertThat (erlLength notMatching) (isEqualTo 3) + ) + + test ("sum returns sum", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 4]" + assertThat (lists.sum xs) (isEqualTo 10)) + + test ("sum returns float sum", fun _ -> + let xs: BeamList = emitErlExpr () "[1.5, 2.5, 3.0]" + assertThat (lists.sum xs) (isEqualTo 7.0)) + + test ("max returns maximum", fun _ -> + let xs: BeamList = emitErlExpr () "[3, 1, 4, 1, 5, 9, 2]" + assertThat (lists.max xs) (isEqualTo 9)) + + test ("min returns minimum", fun _ -> + let xs: BeamList = emitErlExpr () "[3, 1, 4, 1, 5, 9, 2]" + assertThat (lists.min xs) (isEqualTo 1)) + + test ("seq generates integer sequence", fun _ -> + let xs = lists.seq (1, 5) + assertThat (erlLength xs) (isEqualTo 5) + assertThat (lists.nth (1, xs)) (isEqualTo 1) + assertThat (lists.nth (5, xs)) (isEqualTo 5) + ) + + test ("seq with step generates sequence", fun _ -> + let xs = lists.seq (0, 10, 2) + assertThat (erlLength xs) (isEqualTo 6) + assertThat (lists.nth (1, xs)) (isEqualTo 0) + assertThat (lists.nth (2, xs)) (isEqualTo 2) + ) + + test ("duplicate creates repeated list", fun _ -> + let xs: BeamList = lists.duplicate (3, 7) + assertThat (erlLength xs) (isEqualTo 3) + assertThat (lists.nth (1, xs)) (isEqualTo 7) + assertThat (lists.nth (3, xs)) (isEqualTo 7) + ) + + test ("takewhile takes while predicate holds", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" + let result = lists.takewhile ((fun x -> x < 4), xs) + assertThat (erlLength result) (isEqualTo 3) + assertThat (lists.nth (3, result)) (isEqualTo 3) + ) + + test ("dropwhile drops while predicate holds", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" + let result = lists.dropwhile ((fun x -> x < 4), xs) + assertThat (erlLength result) (isEqualTo 2) + assertThat (lists.nth (1, result)) (isEqualTo 4) + ) + + test ("splitwith splits at predicate boundary", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" + let (before, after) = lists.splitwith ((fun x -> x < 3), xs) + assertThat (erlLength before) (isEqualTo 2) + assertThat (erlLength after) (isEqualTo 3) + ) + + test ("delete removes first occurrence", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 2, 1]" + let result = lists.delete (2, xs) + assertThat (erlLength result) (isEqualTo 4) + assertThat (lists.``member`` (2, result)) (isTrue) + ) + + test ("subtract removes elements", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 4, 5]" + let ys: BeamList = emitErlExpr () "[2, 4]" + let result = lists.subtract (xs, ys) + assertThat (erlLength result) (isEqualTo 3) + assertThat (lists.``member`` (2, result)) (isFalse) + ) + + test ("keysort sorts by Nth element", fun _ -> + let xs: BeamList = emitErlExpr () "[{3, c}, {1, a}, {2, b}]" + let sorted = lists.keysort (1, xs) + let first: int * string = lists.nth (1, sorted) + assertThat (fst first) (isEqualTo 1)) + + test ("keydelete removes first matching tuple", fun _ -> + let xs: BeamList = + emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}, {<<\"a\">>, 3}]" + + let result = lists.keydelete ("a", 1, xs) + assertThat (erlLength result) (isEqualTo 2)) + + test ("keymember checks for key", fun _ -> + let xs: BeamList = emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}]" + assertThat (lists.keymember ("a", 1, xs)) (isTrue) + assertThat (lists.keymember ("c", 1, xs)) (isFalse) + ) + + test ("keyFind returns Some for existing key", fun _ -> + let xs: BeamList = + emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}, {<<\"c\">>, 3}]" + + let found = keyFind "b" 1 xs + assertThat found (isEqualTo (Some ("b", 2)))) + + test ("keyFind returns None for missing key", fun _ -> + let xs: BeamList = emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}]" + assertThat (keyFind "z" 1 xs) (isEqualTo None)) + + test ("keyreplace replaces first matching tuple", fun _ -> + let xs: BeamList = + emitErlExpr () "[{<<\"a\">>, 1}, {<<\"b\">>, 2}, {<<\"a\">>, 3}]" + + let result = lists.keyreplace ("a", 1, xs, ("a", 99)) + assertThat (lists.nth (1, result)) (isEqualTo ("a", 99)) + assertThat (erlLength result) (isEqualTo 3) + ) + + test ("mapfoldl maps and folds left", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + + let (mapped, acc) = lists.mapfoldl ((fun x s -> (x * 2, s + x)), 0, xs) + + assertThat mapped (isEqualTo (emitErlExpr () "[2, 4, 6]")) + assertThat acc (isEqualTo 6) + ) + + test ("mapfoldr maps and folds right", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + + let (mapped, acc) = lists.mapfoldr ((fun x s -> (x * 2, s + x)), 0, xs) + + assertThat mapped (isEqualTo (emitErlExpr () "[2, 4, 6]")) + assertThat acc (isEqualTo 6) + ) + + test ("map applies a function to each element", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (lists.map ((fun x -> x * 2), xs)) (isEqualTo (emitErlExpr () "[2, 4, 6]"))) + + test ("filter keeps matching elements", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3, 4]" + assertThat (lists.filter ((fun x -> x % 2 = 0), xs)) (isEqualTo (emitErlExpr () "[2, 4]"))) + + test ("foldl folds from the left", fun _ -> + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (lists.foldl ((fun x acc -> acc + x), 0, xs)) (isEqualTo 6)) + + test ("foldr folds from the right", fun _ -> + // Subtraction is not associative, so this pins the direction: 1-(2-(3-0)) = 2. + let xs: BeamList = emitErlExpr () "[1, 2, 3]" + assertThat (lists.foldr ((fun x acc -> x - acc), 0, xs)) (isEqualTo 2)) + + test ("all and any check predicates", fun _ -> + let xs: BeamList = emitErlExpr () "[2, 4, 6]" + assertThat (lists.all ((fun x -> x % 2 = 0), xs)) (isTrue) + assertThat (lists.any ((fun x -> x > 5), xs)) (isTrue) + assertThat (lists.any ((fun x -> x > 10), xs)) (isFalse) + ) + + test ("sort with a comparison function", fun _ -> + let xs: BeamList = emitErlExpr () "[3, 1, 2]" + // Descending: the comparator returns true when A should come before B. + assertThat (lists.sort ((fun a b -> a >= b), xs)) (isEqualTo (emitErlExpr () "[3, 2, 1]"))) ] + ) diff --git a/test/TestLogger.fs b/test/TestLogger.fs index 81df120..ee2e731 100644 --- a/test/TestLogger.fs +++ b/test/TestLogger.fs @@ -1,188 +1,138 @@ module Fable.Beam.Tests.Logger -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test open Fable.Core - -#if FABLE_COMPILER open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.Maps open Fable.Beam.Logger -#endif - -[] -let ``test logger.info works`` () = -#if FABLE_COMPILER - logger.info "test info message" -#else - () -#endif - -[] -let ``test logger.warning works`` () = -#if FABLE_COMPILER - logger.warning "test warning message" -#else - () -#endif - -[] -let ``test logger.debug works`` () = -#if FABLE_COMPILER - logger.debug "test debug message" -#else - () -#endif - -[] -let ``test logger.info with format args`` () = -#if FABLE_COMPILER - // The 2-arg overload accepts both metadata maps and format args lists - logger.info ("test ~p message", U2.Case2 [ box 42 ]) -#else - () -#endif - -[] -let ``test logger add and remove handler`` () = -#if FABLE_COMPILER - // Round-trip a handler through add_handler/3 and remove_handler/1, asserting the - // ok | {error, term()} result maps to Ok () (and is not swallowed). - let handlerId = Erlang.binaryToAtom "test_handler" - let modle = Erlang.binaryToAtom "logger_std_h" - - let config: BeamMap = - Maps.ofList [ (Erlang.binaryToAtom "level", box (Erlang.binaryToAtom "info")) ] - - logger.add_handler (handlerId, modle, config) |> equal (Ok()) - logger.remove_handler handlerId |> equal (Ok()) -#else - () -#endif - -// ============================================================================ -// Filter helpers -// ============================================================================ - -[] -let ``test Filter.addPrimary receives the event and can stop it`` () = -#if FABLE_COMPILER - // Primary filters run in the logging (client) process, so the filter can record - // what it saw in this process's dictionary for us to assert on afterwards. - let filterId = Erlang.binaryToAtom "fable_test_filter" - let seenKey = Erlang.binaryToAtom "fable_test_seen_level" - let timeKey = Erlang.binaryToAtom "fable_test_seen_time" - - let filter = - System.Func<_, _, _>(fun (ev: Filter.LogEvent) _extra -> - // Exercise the accessors, then record what we saw and discard the event - // (returning `stop` keeps test output clean). `meta` values are Dynamic; - // OTP always stamps a `time` (system time in microseconds) onto the event. - Filter.msg ev |> ignore - let m = Filter.meta ev - Erlang.put timeKey (maps.get (Erlang.binaryToAtom "time", m)) |> ignore - Erlang.put seenKey (Filter.level ev) |> ignore - Filter.stop) - - Filter.addPrimary filterId filter (Erlang.binaryToAtom "ok") |> equal (Ok()) - - // `error` is above the default primary level (`notice`), so it reaches the filter. - logger.error "trigger for filter" - - match Erlang.get seenKey with - | Some lvl -> lvl |> equal LogLevel.Error - | None -> equal "filter saw the event" "filter did not run" - - // The metadata `time` came out as Dynamic and decodes to a positive integer. - match Erlang.get timeKey with - | Some d -> - match Decode.int d with - | Ok t -> (t > 0) |> equal true - | Error _ -> equal "time decodes to int" "decode failed" - | None -> equal "time present in meta" "time missing" - - Filter.removePrimary filterId |> equal (Ok()) -#else - () -#endif - -[] -let ``test Filter.removePrimary on unknown id returns Error`` () = -#if FABLE_COMPILER - match Filter.removePrimary (Erlang.binaryToAtom "fable_test_no_such_filter") with - | Error _ -> equal true true - | Ok() -> equal "Error" "Ok" -#else - () -#endif - -[] -let ``test raw add_primary_filter ok path is not swallowed`` () = -#if FABLE_COMPILER - // The opaque {FilterFun, Extra} tuple that Filter.addPrimary builds for you. - // Exercises the bare-ok success path of the raw IExports binding. - let filterId = Erlang.binaryToAtom "fable_test_raw_filter" - - let filterTuple: obj = - emitErlExpr () "{fun(RawLogEvent__, _) -> RawLogEvent__ end, ok}" - - logger.add_primary_filter (filterId, filterTuple) |> equal (Ok()) - Filter.removePrimary filterId |> equal (Ok()) -#else - () -#endif - -// ============================================================================ -// Primary config -// ============================================================================ - -[] -let ``test set_primary_config ok path is not swallowed`` () = -#if FABLE_COMPILER - // Setting filter_default to its default (`log`) is behaviourally a no-op but - // exercises the bare-ok success path — a missing wrapper would fall through. - logger.set_primary_config (Erlang.binaryToAtom "filter_default", Erlang.binaryToAtom "log") - |> equal (Ok()) -#else - () -#endif - -// ============================================================================ -// Formatter helpers -// ============================================================================ - -[] -let ``test Formatter.setTemplate updates a handler's formatter`` () = -#if FABLE_COMPILER - let handlerId = Erlang.binaryToAtom "fable_test_fmt_handler" - let modle = Erlang.binaryToAtom "logger_std_h" - - let config: BeamMap = - Maps.ofList [ (Erlang.binaryToAtom "level", box (Erlang.binaryToAtom "info")) ] - - logger.add_handler (handlerId, modle, config) |> equal (Ok()) - - // Compact template exercising key / text / cond template items. - let sp = Formatter.text " " - - let template = - [ Formatter.key (Erlang.binaryToAtom "time") - sp - Formatter.key (Erlang.binaryToAtom "level") - Formatter.text ": " - Formatter.cond - (Erlang.binaryToAtom "pid") - [ Formatter.text "[" - Formatter.key (Erlang.binaryToAtom "pid") - Formatter.text "] " ] - [] - Formatter.key (Erlang.binaryToAtom "msg") - Formatter.text "\n" ] - - Formatter.setTemplate handlerId true template |> equal (Ok()) - - logger.remove_handler handlerId |> equal (Ok()) -#else - () -#endif + +let tests = + testList ( + "Logger", + [ test ("logger.info works", fun _ -> + logger.info "test info message" + ) + + test ("logger.warning works", fun _ -> + logger.warning "test warning message" + ) + + test ("logger.debug works", fun _ -> + logger.debug "test debug message" + ) + + test ("logger.info with format args", fun _ -> + // The 2-arg overload accepts both metadata maps and format args lists + logger.info ("test ~p message", U2.Case2 [ box 42 ]) + ) + + test ("logger add and remove handler", fun _ -> + // Round-trip a handler through add_handler/3 and remove_handler/1, asserting the + // ok | {error, term()} result maps to Ok () (and is not swallowed). + let handlerId = Erlang.binaryToAtom "test_handler" + let modle = Erlang.binaryToAtom "logger_std_h" + + let config: BeamMap = + Maps.ofList [ (Erlang.binaryToAtom "level", box (Erlang.binaryToAtom "info")) ] + + assertThat (logger.add_handler (handlerId, modle, config)) (isEqualTo (Ok ())) + assertThat (logger.remove_handler handlerId) (isEqualTo (Ok ())) + ) + + test ("Filter.addPrimary receives the event and can stop it", fun _ -> + // Primary filters run in the logging (client) process, so the filter can record + // what it saw in this process's dictionary for us to assert on afterwards. + let filterId = Erlang.binaryToAtom "fable_test_filter" + let seenKey = Erlang.binaryToAtom "fable_test_seen_level" + let timeKey = Erlang.binaryToAtom "fable_test_seen_time" + + let filter = + System.Func<_, _, _>(fun (ev: Filter.LogEvent) _extra -> + // Exercise the accessors, then record what we saw and discard the event + // (returning `stop` keeps test output clean). `meta` values are Dynamic; + // OTP always stamps a `time` (system time in microseconds) onto the event. + Filter.msg ev |> ignore + let m = Filter.meta ev + Erlang.put timeKey (maps.get (Erlang.binaryToAtom "time", m)) |> ignore + Erlang.put seenKey (Filter.level ev) |> ignore + Filter.stop) + + assertThat (Filter.addPrimary filterId filter (Erlang.binaryToAtom "ok")) (isEqualTo (Ok ())) + + // `error` is above the default primary level (`notice`), so it reaches the filter. + logger.error "trigger for filter" + + match Erlang.get seenKey with + | Some lvl -> assertThat lvl (isEqualTo LogLevel.Error) + | None -> failwith "filter saw the event" + + // The metadata `time` came out as Dynamic and decodes to a positive integer. + match Erlang.get timeKey with + | Some d -> + match Decode.int d with + | Ok t -> assertThat ((t > 0)) (isTrue) + | Error _ -> failwith "time decodes to int" + | None -> failwith "time present in meta" + + assertThat (Filter.removePrimary filterId) (isEqualTo (Ok ())) + ) + + test ("Filter.removePrimary on unknown id returns Error", fun _ -> + match Filter.removePrimary (Erlang.binaryToAtom "fable_test_no_such_filter") with + | Error _ -> assertThat true (isTrue) + | Ok () -> failwith "Error" + ) + + test ("raw add_primary_filter ok path is not swallowed", fun _ -> + // The opaque {FilterFun, Extra} tuple that Filter.addPrimary builds for you. + // Exercises the bare-ok success path of the raw IExports binding. + let filterId = Erlang.binaryToAtom "fable_test_raw_filter" + + let filterTuple: obj = + emitErlExpr () "{fun(RawLogEvent__, _) -> RawLogEvent__ end, ok}" + + assertThat (logger.add_primary_filter (filterId, filterTuple)) (isEqualTo (Ok ())) + assertThat (Filter.removePrimary filterId) (isEqualTo (Ok ())) + ) + + test ("set_primary_config ok path is not swallowed", fun _ -> + // Setting filter_default to its default (`log`) is behaviourally a no-op but + // exercises the bare-ok success path — a missing wrapper would fall through. + assertThat (logger.set_primary_config (Erlang.binaryToAtom "filter_default", Erlang.binaryToAtom "log")) (isEqualTo (Ok ())) + ) + + test ("Formatter.setTemplate updates a handler's formatter", fun _ -> + let handlerId = Erlang.binaryToAtom "fable_test_fmt_handler" + let modle = Erlang.binaryToAtom "logger_std_h" + + let config: BeamMap = + Maps.ofList [ (Erlang.binaryToAtom "level", box (Erlang.binaryToAtom "info")) ] + + assertThat (logger.add_handler (handlerId, modle, config)) (isEqualTo (Ok ())) + + // Compact template exercising key / text / cond template items. + let sp = Formatter.text " " + + let template = + [ Formatter.key (Erlang.binaryToAtom "time") + sp + Formatter.key (Erlang.binaryToAtom "level") + Formatter.text ": " + Formatter.cond + (Erlang.binaryToAtom "pid") + [ Formatter.text "[" + Formatter.key (Erlang.binaryToAtom "pid") + Formatter.text "] " ] + [] + Formatter.key (Erlang.binaryToAtom "msg") + Formatter.text "\n" ] + + assertThat (Formatter.setTemplate handlerId true template) (isEqualTo (Ok ())) + + assertThat (logger.remove_handler handlerId) (isEqualTo (Ok ())) + ) ] + ) diff --git a/test/TestMaps.fs b/test/TestMaps.fs index 33ec4b1..4fd3c5f 100644 --- a/test/TestMaps.fs +++ b/test/TestMaps.fs @@ -1,193 +1,163 @@ module Fable.Beam.Tests.Maps -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Beam.Lists open Fable.Beam.Maps [] let private listLen (xs: BeamList<'T>) : int = nativeOnly -#endif - -[] -let ``test maps.new_ creates empty map`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - maps.size m |> equal 0 -#else - () -#endif - -[] -let ``test maps.put and get`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - let m = maps.put ("key", "value", m) - maps.get ("key", m) |> equal "value" -#else - () -#endif - -[] -let ``test maps.is_key works`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - let m = maps.put ("a", 1, m) - maps.is_key ("a", m) |> equal true - maps.is_key ("b", m) |> equal false -#else - () -#endif - -[] -let ``test maps.remove works`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - let m = maps.put ("a", 1, m) - let m = maps.remove ("a", m) - maps.size m |> equal 0 -#else - () -#endif - -[] -let ``test maps.size works`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - let m = maps.put ("a", 1, m) - let m = maps.put ("b", 2, m) - maps.size m |> equal 2 -#else - () -#endif - -[] -let ``test maps.merge works`` () = -#if FABLE_COMPILER - let m1: BeamMap = maps.put ("a", 1, maps.new_ ()) - let m2 = maps.put ("b", 2, maps.new_ ()) - let merged = maps.merge (m1, m2) - maps.size merged |> equal 2 -#else - () -#endif - -[] -let ``test maps.keys and values`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - let m = maps.put ("a", 1, m) - let m = maps.put ("b", 2, m) - maps.keys m |> Array.length |> equal 2 - maps.values m |> Array.length |> equal 2 -#else - () -#endif - -[] -let ``test maps.get with default`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - maps.get ("missing", m, 42) |> equal 42 -#else - () -#endif - -[] -let ``test maps.to_list and from_list`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - let m = maps.put ("a", 1, m) - let lst = maps.to_list m - Array.length lst |> equal 1 - let m2 = maps.from_list lst - maps.size m2 |> equal 1 -#else - () -#endif - -[] -let ``test tryFind returns Some for existing key`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.put ("x", 99, maps.new_ ()) - tryFind "x" m |> equal (Some 99) -#else - () -#endif - -[] -let ``test tryFind returns None for missing key`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - tryFind "missing" m |> equal None -#else - () -#endif - -[] -let ``test ofList builds a map from a literal list`` () = -#if FABLE_COMPILER - let headers: BeamMap = - ofList [ "content-type", "text/html"; "server", "cowboy" ] - - maps.size headers |> equal 2 - maps.get ("content-type", headers) |> equal "text/html" - tryFind "server" headers |> equal (Some "cowboy") -#else - () -#endif - -[] -let ``test keysRaw and valuesRaw return native lists matching keys and values`` () = -#if FABLE_COMPILER - let m: BeamMap = ofList [ "a", 1; "b", 2; "c", 3 ] - // native lists carry the same data as the array-returning members, without the ref-wrap - keysRaw m |> listLen |> equal (maps.keys m |> Array.length) - valuesRaw m |> listLen |> equal (maps.values m |> Array.length) - keysRaw m |> listLen |> equal 3 -#else - () -#endif - -[] -let ``test toListRaw returns native list of pairs`` () = -#if FABLE_COMPILER - let m: BeamMap = ofList [ "a", 1; "b", 2 ] - toListRaw m |> listLen |> equal 2 -#else - () -#endif - -[] -let ``test maps.fold accumulates over key-value pairs`` () = -#if FABLE_COMPILER - // maps:fold/3 applies F(K, V, Acc) — the only 3-arity callback in the bindings. - let m: BeamMap = ofList [ ("a", 1); ("b", 2); ("c", 3) ] - maps.fold ((fun _k v acc -> v + acc), 0, m) |> equal 6 -#else - () -#endif - -[] -let ``test maps.map transforms each value`` () = -#if FABLE_COMPILER - let m: BeamMap = ofList [ ("a", 1); ("b", 2) ] - let doubled = maps.map ((fun _k v -> v * 2), m) - maps.get ("a", doubled) |> equal 2 - maps.get ("b", doubled) |> equal 4 -#else - () -#endif - -[] -let ``test maps.filter keeps matching pairs`` () = -#if FABLE_COMPILER - let m: BeamMap = ofList [ ("a", 1); ("b", 2); ("c", 3) ] - let big = maps.filter ((fun _k v -> v > 1), m) - maps.size big |> equal 2 - maps.is_key ("a", big) |> equal false -#else - () -#endif + +let tests = + testList ( + "Maps", + [ test ( + "new_ creates an empty map", + fun _ -> + let m: BeamMap = maps.new_ () + assertThat (maps.size m) (isEqualTo 0) + ) + + test ( + "put and get round-trip", + fun _ -> + let m: BeamMap = maps.new_ () + let m = maps.put ("key", "value", m) + assertThat (maps.get ("key", m)) (isEqualTo "value") + ) + + test ( + "is_key works", + fun _ -> + let m: BeamMap = maps.new_ () + let m = maps.put ("a", 1, m) + assertThat (maps.is_key ("a", m)) (isEqualTo true) + assertThat (maps.is_key ("b", m)) (isEqualTo false) + ) + + test ( + "remove works", + fun _ -> + let m: BeamMap = maps.new_ () + let m = maps.put ("a", 1, m) + let m = maps.remove ("a", m) + assertThat (maps.size m) (isEqualTo 0) + ) + + test ( + "size works", + fun _ -> + let m: BeamMap = maps.new_ () + let m = maps.put ("a", 1, m) + let m = maps.put ("b", 2, m) + assertThat (maps.size m) (isEqualTo 2) + ) + + test ( + "merge works", + fun _ -> + let m1: BeamMap = maps.put ("a", 1, maps.new_ ()) + let m2 = maps.put ("b", 2, maps.new_ ()) + let merged = maps.merge (m1, m2) + assertThat (maps.size merged) (isEqualTo 2) + ) + + test ( + "keys and values", + fun _ -> + let m: BeamMap = maps.new_ () + let m = maps.put ("a", 1, m) + let m = maps.put ("b", 2, m) + assertThat (maps.keys m |> Array.length) (isEqualTo 2) + assertThat (maps.values m |> Array.length) (isEqualTo 2) + ) + + test ( + "get with default", + fun _ -> + let m: BeamMap = maps.new_ () + assertThat (maps.get ("missing", m, 42)) (isEqualTo 42) + ) + + test ( + "to_list and from_list", + fun _ -> + let m: BeamMap = maps.new_ () + let m = maps.put ("a", 1, m) + let lst = maps.to_list m + assertThat (Array.length lst) (isEqualTo 1) + let m2 = maps.from_list lst + assertThat (maps.size m2) (isEqualTo 1) + ) + + test ( + "tryFind returns Some for existing key", + fun _ -> + let m: BeamMap = maps.put ("x", 99, maps.new_ ()) + assertThat (tryFind "x" m) (isEqualTo (Some 99)) + ) + + test ( + "tryFind returns None for missing key", + fun _ -> + let m: BeamMap = maps.new_ () + assertThat (tryFind "missing" m) (isEqualTo None) + ) + + test ( + "ofList builds a map from a literal list", + fun _ -> + let headers: BeamMap = + ofList [ "content-type", "text/html"; "server", "cowboy" ] + + assertThat (maps.size headers) (isEqualTo 2) + assertThat (maps.get ("content-type", headers)) (isEqualTo "text/html") + assertThat (tryFind "server" headers) (isEqualTo (Some "cowboy")) + ) + + test ( + "keysRaw and valuesRaw return native lists matching keys and values", + fun _ -> + let m: BeamMap = ofList [ "a", 1; "b", 2; "c", 3 ] + // native lists carry the same data as the array-returning members, without the ref-wrap + assertThat (keysRaw m |> listLen) (isEqualTo (maps.keys m |> Array.length)) + assertThat (valuesRaw m |> listLen) (isEqualTo (maps.values m |> Array.length)) + assertThat (keysRaw m |> listLen) (isEqualTo 3) + ) + + test ( + "toListRaw returns native list of pairs", + fun _ -> + let m: BeamMap = ofList [ "a", 1; "b", 2 ] + assertThat (toListRaw m |> listLen) (isEqualTo 2) + ) + + test ( + "maps.fold accumulates over key-value pairs", + fun _ -> + // maps:fold/3 applies F(K, V, Acc) — the only 3-arity callback in the bindings. + let m: BeamMap = ofList [ ("a", 1); ("b", 2); ("c", 3) ] + assertThat (maps.fold ((fun _k v acc -> v + acc), 0, m)) (isEqualTo 6) + ) + + test ( + "maps.map transforms each value", + fun _ -> + let m: BeamMap = ofList [ ("a", 1); ("b", 2) ] + let doubled = maps.map ((fun _k v -> v * 2), m) + assertThat (maps.get ("a", doubled)) (isEqualTo 2) + assertThat (maps.get ("b", doubled)) (isEqualTo 4) + ) + + test ( + "maps.filter keeps matching pairs", + fun _ -> + let m: BeamMap = ofList [ ("a", 1); ("b", 2); ("c", 3) ] + let big = maps.filter ((fun _k v -> v > 1), m) + assertThat (maps.size big) (isEqualTo 2) + assertThat (maps.is_key ("a", big)) (isEqualTo false) + ) ] + ) diff --git a/test/TestMath.fs b/test/TestMath.fs index 770e107..7859bac 100644 --- a/test/TestMath.fs +++ b/test/TestMath.fs @@ -1,114 +1,51 @@ module Fable.Beam.Tests.Math -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.Math -#endif -[] -let ``test math.pi returns pi`` () = -#if FABLE_COMPILER - let pi = math.pi () - // pi is approximately 3.14159 - (pi > 3.14 && pi < 3.15) |> equal true -#else - () -#endif +let tests = + testList ( + "Math", + [ test ("pi returns pi", fun _ -> + let pi = math.pi () + // pi is approximately 3.14159 + assertThat (pi > 3.14 && pi < 3.15) (isTrue)) -[] -let ``test math.sin of zero is zero`` () = -#if FABLE_COMPILER - math.sin 0.0 |> equal 0.0 -#else - () -#endif + test ("sin of zero is zero", fun _ -> assertThat (math.sin 0.0) (isEqualTo 0.0)) -[] -let ``test math.cos of zero is one`` () = -#if FABLE_COMPILER - math.cos 0.0 |> equal 1.0 -#else - () -#endif + test ("cos of zero is one", fun _ -> assertThat (math.cos 0.0) (isEqualTo 1.0)) -[] -let ``test math.sqrt of four is two`` () = -#if FABLE_COMPILER - math.sqrt 4.0 |> equal 2.0 -#else - () -#endif + test ("sqrt of four is two", fun _ -> assertThat (math.sqrt 4.0) (isEqualTo 2.0)) -[] -let ``test math.pow computes power`` () = -#if FABLE_COMPILER - math.pow (2.0, 10.0) |> equal 1024.0 -#else - () -#endif + test ("pow computes power", fun _ -> assertThat (math.pow (2.0, 10.0)) (isEqualTo 1024.0)) -[] -let ``test math.exp of zero is one`` () = -#if FABLE_COMPILER - math.exp 0.0 |> equal 1.0 -#else - () -#endif + test ("exp of zero is one", fun _ -> assertThat (math.exp 0.0) (isEqualTo 1.0)) -[] -let ``test math.log of e is one`` () = -#if FABLE_COMPILER - let e = math.exp 1.0 - let result = math.log e - // result should be approximately 1.0 - (result > 0.9999 && result < 1.0001) |> equal true -#else - () -#endif + test ("log of e is one", fun _ -> + let e = math.exp 1.0 + let result = math.log e + // result should be approximately 1.0 + assertThat (result > 0.9999 && result < 1.0001) (isTrue)) -[] -let ``test math.log2 of eight is three`` () = -#if FABLE_COMPILER - let result = math.log2 8.0 - (result > 2.9999 && result < 3.0001) |> equal true -#else - () -#endif + test ("log2 of eight is three", fun _ -> + let result = math.log2 8.0 + assertThat (result > 2.9999 && result < 3.0001) (isTrue)) -[] -let ``test math.log10 of one hundred is two`` () = -#if FABLE_COMPILER - let result = math.log10 100.0 - (result > 1.9999 && result < 2.0001) |> equal true -#else - () -#endif + test ("log10 of one hundred is two", fun _ -> + let result = math.log10 100.0 + assertThat (result > 1.9999 && result < 2.0001) (isTrue)) -[] -let ``test math.floor rounds down`` () = -#if FABLE_COMPILER - math.floor 3.9 |> equal 3.0 -#else - () -#endif + test ("floor rounds down", fun _ -> assertThat (math.floor 3.9) (isEqualTo 3.0)) -[] -let ``test math.ceil rounds up`` () = -#if FABLE_COMPILER - math.ceil 3.1 |> equal 4.0 -#else - () -#endif + test ("ceil rounds up", fun _ -> assertThat (math.ceil 3.1) (isEqualTo 4.0)) -[] -let ``test math.atan2 quadrant`` () = -#if FABLE_COMPILER - // atan2(1, 1) = pi/4 approximately 0.785 - let result = math.atan2 (1.0, 1.0) - (result > 0.78 && result < 0.79) |> equal true -#else - () -#endif + test ("atan2 quadrant", fun _ -> + // atan2(1, 1) = pi/4 approximately 0.785 + let result = math.atan2 (1.0, 1.0) + assertThat (result > 0.78 && result < 0.79) (isTrue)) ] + ) diff --git a/test/TestOs.fs b/test/TestOs.fs index 5cbe2a4..040789b 100644 --- a/test/TestOs.fs +++ b/test/TestOs.fs @@ -1,129 +1,78 @@ module Fable.Beam.Tests.Os -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Beam open Fable.Beam.Os -#endif -[] -let ``test getenv returns None for unset var`` () = -#if FABLE_COMPILER - getenv "FABLE_BEAM_TEST_UNSET_12345" |> equal None -#else - () -#endif +let tests = + testList ( + "Os", + [ test ("getenv returns None for unset var", fun _ -> + assertThat (getenv "FABLE_BEAM_TEST_UNSET_12345") (isEqualTo None)) -[] -let ``test putenv and getenv roundtrip`` () = -#if FABLE_COMPILER - putenv "FABLE_BEAM_TEST_VAR" "hello_beam" - getenv "FABLE_BEAM_TEST_VAR" |> equal (Some "hello_beam") - unsetenv "FABLE_BEAM_TEST_VAR" -#else - () -#endif + test ("putenv and getenv roundtrip", fun _ -> + putenv "FABLE_BEAM_TEST_VAR" "hello_beam" + assertThat (getenv "FABLE_BEAM_TEST_VAR") (isEqualTo (Some "hello_beam")) + unsetenv "FABLE_BEAM_TEST_VAR" + ) -[] -let ``test unsetenv removes a variable`` () = -#if FABLE_COMPILER - putenv "FABLE_BEAM_TEST_UNSET" "temp" - unsetenv "FABLE_BEAM_TEST_UNSET" - getenv "FABLE_BEAM_TEST_UNSET" |> equal None -#else - () -#endif + test ("unsetenv removes a variable", fun _ -> + putenv "FABLE_BEAM_TEST_UNSET" "temp" + unsetenv "FABLE_BEAM_TEST_UNSET" + assertThat (getenv "FABLE_BEAM_TEST_UNSET") (isEqualTo None) + ) -[] -let ``test getenv returns Some for HOME`` () = -#if FABLE_COMPILER - match getenv "HOME" with - | Some home -> (String.length home > 0) |> equal true - | None -> - // HOME should be set on any unix system - equal "Some" "None" -#else - () -#endif + test ("getenv returns Some for HOME", fun _ -> + match getenv "HOME" with + | Some home -> assertThat ((String.length home > 0)) (isTrue) + | None -> + // HOME should be set on any unix system + failwith "expected HOME to be set" + ) -[] -let ``test cmd runs a command`` () = -#if FABLE_COMPILER - let result = cmd "echo hello" - result |> equal "hello\n" -#else - () -#endif + test ("cmd runs a command", fun _ -> + let result = cmd "echo hello" + assertThat result (isEqualTo "hello\n")) -[] -let ``test systemTimeSeconds returns positive`` () = -#if FABLE_COMPILER - let t = systemTimeSeconds () - (t > 0) |> equal true -#else - () -#endif + test ("systemTimeSeconds returns positive", fun _ -> + let t = systemTimeSeconds () + assertThat ((t > 0)) (isTrue)) -[] -let ``test systemTime with TimeUnit returns a sensible value`` () = -#if FABLE_COMPILER - // Exercises the TimeUnit DU: each case compiles to its time-unit atom. - let secs = systemTime TimeUnit.Second - (secs > 1_000_000_000L) |> equal true - let micros = systemTime TimeUnit.Microsecond - (micros > secs) |> equal true -#else - () -#endif + test ("systemTime with TimeUnit returns a sensible value", fun _ -> + // Exercises the TimeUnit DU: each case compiles to its time-unit atom. + let secs = systemTime TimeUnit.Second + assertThat ((secs > 1_000_000_000L)) (isTrue) + let micros = systemTime TimeUnit.Microsecond + assertThat ((micros > secs)) (isTrue) + ) -[] -let ``test systemTimeMs is monotonically increasing`` () = -#if FABLE_COMPILER - let t1 = systemTimeMs () - let t2 = systemTimeMs () - (t2 >= t1) |> equal true -#else - () -#endif + test ("systemTimeMs is monotonically increasing", fun _ -> + let t1 = systemTimeMs () + let t2 = systemTimeMs () + assertThat ((t2 >= t1)) (isTrue)) -[] -let ``test systemTimeMs returns int64 value above 32-bit range`` () = -#if FABLE_COMPILER - let t = systemTimeMs () - // Millisecond timestamps are around 1.7 * 10^12, well above int32 max (~2.1 * 10^9) - (t > 1_000_000_000_000L) |> equal true -#else - () -#endif + test ("systemTimeMs returns int64 value above 32-bit range", fun _ -> + let t = systemTimeMs () + // Millisecond timestamps are around 1.7 * 10^12, well above int32 max (~2.1 * 10^9) + assertThat ((t > 1_000_000_000_000L)) (isTrue)) -[] -let ``test systemTimeSeconds returns int64`` () = -#if FABLE_COMPILER - let t = systemTimeSeconds () - // Unix epoch seconds are around 1.7 * 10^9 - (t > 1_000_000_000L) |> equal true -#else - () -#endif + test ("systemTimeSeconds returns int64", fun _ -> + let t = systemTimeSeconds () + // Unix epoch seconds are around 1.7 * 10^9 + assertThat ((t > 1_000_000_000L)) (isTrue)) -[] -let ``test osType returns a string tuple`` () = -#if FABLE_COMPILER - let (family, _name) = osType () - // Should be "unix" on Linux/macOS or "win32" on Windows - (family = "unix" || family = "win32") |> equal true -#else - () -#endif + test ("osType returns a string tuple", fun _ -> + let (family, _name) = osType () + // Should be "unix" on Linux/macOS or "win32" on Windows + assertThat ((family = "unix" || family = "win32")) (isTrue)) -[] -let ``test version returns an int tuple`` () = -#if FABLE_COMPILER - let (major, minor, release) = version () - (major >= 0) |> equal true - (minor >= 0) |> equal true - (release >= 0) |> equal true -#else - () -#endif + test ("version returns an int tuple", fun _ -> + let (major, minor, release) = version () + assertThat ((major >= 0)) (isTrue) + assertThat ((minor >= 0)) (isTrue) + assertThat ((release >= 0)) (isTrue) + ) ] + ) diff --git a/test/TestPort.fs b/test/TestPort.fs index 111b09d..7c8183e 100644 --- a/test/TestPort.fs +++ b/test/TestPort.fs @@ -1,12 +1,14 @@ module Fable.Beam.Tests.Port -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.Port +#if FABLE_COMPILER /// A single-case message used to place an unrelated message in the test process's /// mailbox and verify that selective port receive leaves it there. The nullary /// case compiles to the bare Erlang atom `unrelated_probe`, matching how the @@ -19,247 +21,192 @@ let private options arguments maxLineLength = maxLineLength = maxLineLength } #endif -[] -let ``test port streams a complete line and exit status`` () = -#if FABLE_COMPILER - // This is a deliberately small fixture: sh reads one stdin line, echoes it, - // and exits. The script is an argument to a fixed executable, not a command - // assembled by the port binding. - let script = "read line; printf '%s\n' \"$line\"; exit 7" - - let port = - match startAbsolute "/bin/sh" (options [ "-c"; script ] 128) with - | Ok port -> port - | Error reason -> failwithf "could not start port fixture: %s" reason - - send port "hello from port\n" |> equal true - - match receive port 1000 with - | Some(Line data) -> equal "hello from port" data - | Some message -> failwithf "expected line data, got %A" message - | None -> failwith "timed out waiting for port line" - - match receive port 1000 with - | Some(ExitStatus status) -> equal 7 status - | Some message -> failwithf "expected exit status, got %A" message - | None -> failwith "timed out waiting for port exit status" -#else - () -#endif - -[] -let ``test port path lookup and close`` () = -#if FABLE_COMPILER - let port = - match startOnPath "cat" (options [] 128) with - | Ok port -> port - | Error reason -> failwithf "could not find cat on PATH: %s" reason - - send port "close probe\n" |> equal true - - match receive port 1000 with - | Some(Line data) -> equal "close probe" data - | Some message -> failwithf "expected line data, got %A" message - | None -> failwith "timed out waiting for port line" - - close port -#else - () -#endif - -[] -let ``test port delivers an incomplete line when the process ends mid-line`` () = -#if FABLE_COMPILER - // `printf` with no trailing newline: the process ends mid-line. With `exit_status` - // enabled, ERTS delivers both the pending partial line (`{data, {noeol, ...}}`) and - // the exit status. Their relative order is an ERTS implementation detail — on this - // runtime the exit status arrives first — so assert on the two trailing messages - // without depending on which one is delivered first. - let port = - match startAbsolute "/bin/sh" (options [ "-c"; "printf 'partial'; exit 3" ] 128) with - | Ok port -> port - | Error reason -> failwithf "could not start port fixture: %s" reason - - let trailing = - [ receive port 1000; receive port 1000 ] - |> List.filter (fun m -> m <> None) - |> List.map Option.get - - (trailing - |> List.exists (function - | IncompleteLine d -> d = "partial" - | _ -> false)) - |> equal true - - (trailing - |> List.exists (function - | ExitStatus s -> s = 3 - | _ -> false)) - |> equal true -#else - () -#endif - -[] -let ``test port receive leaves unrelated mailbox messages behind`` () = -#if FABLE_COMPILER - // Inject an unrelated message into this process's mailbox before the port reads, - // to verify the selective port receive skips it and leaves it for the mailbox owner. - emitErlExpr () "erlang:self() ! unrelated_probe" - - let port = - match startAbsolute "/bin/sh" (options [ "-c"; "read line; printf '%s\n' \"$line\"; exit 0" ] 128) with - | Ok port -> port - | Error reason -> failwithf "could not start port fixture: %s" reason - - send port "selective probe\n" |> equal true - - // Selective receive must skip the unrelated message and deliver the port line. - match receive port 1000 with - | Some(Line data) -> equal "selective probe" data - | Some message -> failwithf "expected line data, got %A" message - | None -> failwith "timed out waiting for port line" - - // Drain the port's exit status; selective receive still skips the unrelated message. - match receive port 1000 with - | Some(ExitStatus status) -> equal 0 status - | Some message -> failwithf "expected exit status, got %A" message - | None -> failwith "timed out waiting for exit status" - - // The unrelated message must still be in the mailbox, undisturbed. - match Erlang.receive 1000 with - | Some Unrelated -> equal 1 1 - | _ -> failwith "unrelated mailbox message was lost by selective receive" -#else - () -#endif - -[] -let ``test port startAbsolute rejects a relative path`` () = -#if FABLE_COMPILER - match startAbsolute "some/relative/path" (options [] 128) with - | Error reason -> - equal "path must be absolute, maxLineLength must be positive, and stderrToStdout requires useStdio" reason - | Ok _ -> failwith "expected an error for a relative path" -#else - () -#endif - -[] -let ``test port startOnPath errors when the executable is missing`` () = -#if FABLE_COMPILER - match startOnPath "definitely_not_a_real_executable_xyz" (options [] 128) with - | Error reason -> equal "executable not found on PATH" reason - | Ok _ -> failwith "expected an error for a missing executable" -#else - () -#endif - -[] -let ``test port rejects a non-positive maxLineLength`` () = -#if FABLE_COMPILER - match startAbsolute "/bin/sh" (options [] 0) with - | Error reason -> - equal "path must be absolute, maxLineLength must be positive, and stderrToStdout requires useStdio" reason - | Ok _ -> failwith "expected an error for maxLineLength 0" - - match startOnPath "cat" (options [] 0) with - | Error reason -> equal "maxLineLength must be positive and stderrToStdout requires useStdio" reason - | Ok _ -> failwith "expected an error for maxLineLength 0" -#else - () -#endif - -[] -let ``test port options redirect stderr and apply child environment`` () = -#if FABLE_COMPILER - let portOptions = - { options - [ "-c" - "printf '%s:%s\\n' \"$PORT_TEST_VALUE\" \"$PWD\"; printf 'diagnostic\\n' >&2" ] - 128 with - stderrToStdout = true - workingDirectory = Some "/tmp" - environment = [ "PORT_TEST_VALUE", "configured" ] } - - let port = - match startAbsolute "/bin/sh" portOptions with - | Ok port -> port - | Error reason -> failwithf "could not start configured port fixture: %s" reason - - receiveUntil port 1000 - |> List.filter (function - | Line _ -> true - | _ -> false) - |> List.map (function - | Line data -> data - | _ -> failwith "unreachable") - |> equal [ "configured:/tmp"; "diagnostic" ] -#else - () -#endif - -[] -let ``test port lifecycle operations are non-throwing after exit`` () = -#if FABLE_COMPILER - let port = - match startAbsolute "/bin/sh" (options [ "-c"; "exit 0" ] 128) with - | Ok port -> port - | Error reason -> failwithf "could not start port fixture: %s" reason - - receiveUntil port 1000 |> ignore - - match trySend port "too late\n" with - | Error _ -> () - | Ok() -> failwith "sending to an exited port should fail" - - match tryClose port with - | Error _ -> () - | Ok() -> failwith "closing an exited port should fail" -#else - () -#endif - -[] -let ``test port foldMessages keeps trailing oversized JSONL fragments after exit status`` () = -#if FABLE_COMPILER - let port = - match startAbsolute "/bin/sh" (options [ "-c"; "printf '{\\\"value\\\":\\\"0123456789\\\"}'" ] 8) with - | Ok port -> port - | Error reason -> failwithf "could not start port fixture: %s" reason - - let messages = - foldMessages port 1000 (fun messages message -> message :: messages) [] - |> List.rev - - (messages - |> List.exists (function - | ExitStatus 0 -> true - | _ -> false)) - |> equal true - - (messages - |> List.exists (function - | IncompleteLine data -> data.Contains "0123456789" || data.Length = 8 - | _ -> false)) - |> equal true -#else - () -#endif - -[] -let ``test monitored port emits a typed down notification`` () = -#if FABLE_COMPILER - let port = - match startAbsolute "/bin/sh" (options [ "-c"; "exit 0" ] 128) with - | Ok port -> port - | Error reason -> failwithf "could not start port fixture: %s" reason - - let portMonitor = monitor port - - match receiveDown port portMonitor 1000 with - | Some(Down reason) -> reason |> equal "normal" - | None -> failwith "timed out waiting for port down notification" -#else - () -#endif +let tests = + testList ( + "Port", + [ test ("port streams a complete line and exit status", fun _ -> + // This is a deliberately small fixture: sh reads one stdin line, echoes it, + // and exits. The script is an argument to a fixed executable, not a command + // assembled by the port binding. + let script = "read line; printf '%s\n' \"$line\"; exit 7" + + let port = + match startAbsolute "/bin/sh" (options [ "-c"; script ] 128) with + | Ok port -> port + | Error reason -> failwithf "could not start port fixture: %s" reason + + assertThat (send port "hello from port\n") (isTrue) + + match receive port 1000 with + | Some(Line data) -> assertThat data (isEqualTo "hello from port") + | Some message -> failwithf "expected line data, got %A" message + | None -> failwith "timed out waiting for port line" + + match receive port 1000 with + | Some(ExitStatus status) -> assertThat status (isEqualTo 7) + | Some message -> failwithf "expected exit status, got %A" message + | None -> failwith "timed out waiting for port exit status" + ) + + test ("port path lookup and close", fun _ -> + let port = + match startOnPath "cat" (options [] 128) with + | Ok port -> port + | Error reason -> failwithf "could not find cat on PATH: %s" reason + + assertThat (send port "close probe\n") (isTrue) + + match receive port 1000 with + | Some(Line data) -> assertThat data (isEqualTo "close probe") + | Some message -> failwithf "expected line data, got %A" message + | None -> failwith "timed out waiting for port line" + + close port + ) + + test ("port delivers an incomplete line when the process ends mid-line", fun _ -> + // `printf` with no trailing newline: the process ends mid-line. With `exit_status` + // enabled, ERTS delivers both the pending partial line (`{data, {noeol, ...}}`) and + // the exit status. Their relative order is an ERTS implementation detail — on this + // runtime the exit status arrives first — so assert on the two trailing messages + // without depending on which one is delivered first. + let port = + match startAbsolute "/bin/sh" (options [ "-c"; "printf 'partial'; exit 3" ] 128) with + | Ok port -> port + | Error reason -> failwithf "could not start port fixture: %s" reason + + let trailing = + [ receive port 1000; receive port 1000 ] + |> List.filter (fun m -> m <> None) + |> List.map Option.get + + assertThat ((trailing |> List.exists (function | IncompleteLine d -> d = "partial" | _ -> false))) (isTrue) + assertThat ((trailing |> List.exists (function | ExitStatus s -> s = 3 | _ -> false))) (isTrue) + ) + + test ("port receive leaves unrelated mailbox messages behind", fun _ -> + // Inject an unrelated message into this process's mailbox before the port reads, + // to verify the selective port receive skips it and leaves it for the mailbox owner. + emitErlExpr () "erlang:self() ! unrelated_probe" + + let port = + match startAbsolute "/bin/sh" (options [ "-c"; "read line; printf '%s\n' \"$line\"; exit 0" ] 128) with + | Ok port -> port + | Error reason -> failwithf "could not start port fixture: %s" reason + + assertThat (send port "selective probe\n") (isTrue) + + // Selective receive must skip the unrelated message and deliver the port line. + match receive port 1000 with + | Some(Line data) -> assertThat data (isEqualTo "selective probe") + | Some message -> failwithf "expected line data, got %A" message + | None -> failwith "timed out waiting for port line" + + // Drain the port's exit status; selective receive still skips the unrelated message. + match receive port 1000 with + | Some(ExitStatus status) -> assertThat status (isEqualTo 0) + | Some message -> failwithf "expected exit status, got %A" message + | None -> failwith "timed out waiting for exit status" + + // The unrelated message must still be in the mailbox, undisturbed. + match Erlang.receive 1000 with + | Some Unrelated -> assertThat 1 (isEqualTo 1) + | _ -> failwith "unrelated mailbox message was lost by selective receive" + ) + + test ("port startAbsolute rejects a relative path", fun _ -> + match startAbsolute "some/relative/path" (options [] 128) with + | Error reason -> assertThat reason (isEqualTo "path must be absolute, maxLineLength must be positive, and stderrToStdout requires useStdio") + | Ok _ -> failwith "expected an error for a relative path" + ) + + test ("port startOnPath errors when the executable is missing", fun _ -> + match startOnPath "definitely_not_a_real_executable_xyz" (options [] 128) with + | Error reason -> assertThat reason (isEqualTo "executable not found on PATH") + | Ok _ -> failwith "expected an error for a missing executable" + ) + + test ("port rejects a non-positive maxLineLength", fun _ -> + match startAbsolute "/bin/sh" (options [] 0) with + | Error reason -> assertThat reason (isEqualTo "path must be absolute, maxLineLength must be positive, and stderrToStdout requires useStdio") + | Ok _ -> failwith "expected an error for maxLineLength 0" + + match startOnPath "cat" (options [] 0) with + | Error reason -> assertThat reason (isEqualTo "maxLineLength must be positive and stderrToStdout requires useStdio") + | Ok _ -> failwith "expected an error for maxLineLength 0" + ) + + test ("port options redirect stderr and apply child environment", fun _ -> + // The shell script writes two lines to stdout (one with the configured env value + // and working dir) and a diagnostic line to stderr, merged via stderrToStdout. + let script = "printf '%s:%s\\n' \"$PORT_TEST_VALUE\" \"$PWD\"; printf 'diagnostic\\n' >&2" + + let portOptions = + { PortOptions.defaultOptions with + arguments = [ "-c"; script ] + maxLineLength = 128 + stderrToStdout = true + workingDirectory = Some "/tmp" + environment = [ "PORT_TEST_VALUE", "configured" ] } + + let port = + match startAbsolute "/bin/sh" portOptions with + | Ok port -> port + | Error reason -> failwithf "could not start configured port fixture: %s" reason + + let lines = + receiveUntil port 1000 + |> List.filter (function + | Line _ -> true + | _ -> false) + |> List.map (function + | Line data -> data + | _ -> failwith "unreachable") + + assertThat lines (isEqualTo [ "configured:/tmp"; "diagnostic" ]) + ) + + test ("port lifecycle operations are non-throwing after exit", fun _ -> + let port = + match startAbsolute "/bin/sh" (options [ "-c"; "exit 0" ] 128) with + | Ok port -> port + | Error reason -> failwithf "could not start port fixture: %s" reason + + receiveUntil port 1000 |> ignore + + match trySend port "too late\n" with + | Error _ -> assertThat true (isTrue) + | Ok () -> failwith "sending to an exited port should fail" + + match tryClose port with + | Error _ -> assertThat true (isTrue) + | Ok () -> failwith "closing an exited port should fail" + ) + + test ("port foldMessages keeps trailing oversized JSONL fragments after exit", fun _ -> + let port = + match startAbsolute "/bin/sh" (options [ "-c"; "printf '{\\\"value\\\":\\\"0123456789\\\"}'" ] 8) with + | Ok port -> port + | Error reason -> failwithf "could not start port fixture: %s" reason + + let messages = + foldMessages port 1000 (fun messages message -> message :: messages) [] + |> List.rev + + assertThat ((messages |> List.exists (function | ExitStatus 0 -> true | _ -> false))) (isTrue) + assertThat ((messages |> List.exists (function | IncompleteLine data -> data.Contains "0123456789" || data.Length = 8 | _ -> false))) (isTrue) + ) + + test ("monitored port emits a typed down notification", fun _ -> + let port = + match startAbsolute "/bin/sh" (options [ "-c"; "exit 0" ] 128) with + | Ok port -> port + | Error reason -> failwithf "could not start port fixture: %s" reason + + let portMonitor = monitor port + + match receiveDown port portMonitor 1000 with + | Some(Down reason) -> assertThat reason (isEqualTo "normal") + | None -> failwith "timed out waiting for port down notification" + ) ] + ) diff --git a/test/TestProplists.fs b/test/TestProplists.fs index b357d93..6606538 100644 --- a/test/TestProplists.fs +++ b/test/TestProplists.fs @@ -1,152 +1,101 @@ module Fable.Beam.Tests.Proplists -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.Lists open Fable.Beam.Maps open Fable.Beam.Proplists -#endif -[] -let ``test proplists.get_value returns value when key found`` () = -#if FABLE_COMPILER - // [{name, <<"alice">>}, {age, 30}] - let pl: BeamList = emitErlExpr () "[{name, <<\"alice\">>}, {age, 30}]" - let key = Erlang.binaryToAtom "name" - proplists.get_value (key, pl) |> equal (Some "alice") -#else - () -#endif +let tests = + testList ( + "Proplists", + [ test ("get_value returns value when key found", fun _ -> + // [{name, <<"alice">>}, {age, 30}] + let pl: BeamList = emitErlExpr () "[{name, <<\"alice\">>}, {age, 30}]" + let key = Erlang.binaryToAtom "name" + assertThat (proplists.get_value (key, pl)) (isEqualTo (Some "alice"))) -[] -let ``test proplists.get_value returns None when key missing`` () = -#if FABLE_COMPILER - let pl: BeamList = emitErlExpr () "[{name, <<\"alice\">>}]" - let key = Erlang.binaryToAtom "missing" - let result: string option = proplists.get_value (key, pl) - result |> equal None -#else - () -#endif + test ("get_value returns None when key missing", fun _ -> + let pl: BeamList = emitErlExpr () "[{name, <<\"alice\">>}]" + let key = Erlang.binaryToAtom "missing" + let result: string option = proplists.get_value (key, pl) + assertThat result (isEqualTo None)) -[] -let ``test proplists.get_value with default returns value when key found`` () = -#if FABLE_COMPILER - let pl: BeamList = emitErlExpr () "[{port, 443}]" - let portKey = Erlang.binaryToAtom "port" - let timeoutKey = Erlang.binaryToAtom "timeout" - proplists.get_value (portKey, pl, 80) |> equal 443 - proplists.get_value (timeoutKey, pl, 5000) |> equal 5000 -#else - () -#endif + test ("get_value with default returns value when key found", fun _ -> + let pl: BeamList = emitErlExpr () "[{port, 443}]" + let portKey = Erlang.binaryToAtom "port" + let timeoutKey = Erlang.binaryToAtom "timeout" + assertThat (proplists.get_value (portKey, pl, 80)) (isEqualTo 443) + assertThat (proplists.get_value (timeoutKey, pl, 5000)) (isEqualTo 5000)) -[] -let ``test proplists.is_defined returns correct bool`` () = -#if FABLE_COMPILER - let pl: BeamList = emitErlExpr () "[{ssl, true}, {port, 443}]" - let sslKey = Erlang.binaryToAtom "ssl" - let missingKey = Erlang.binaryToAtom "missing" - proplists.is_defined (sslKey, pl) |> equal true - proplists.is_defined (missingKey, pl) |> equal false -#else - () -#endif + test ("is_defined returns correct bool", fun _ -> + let pl: BeamList = emitErlExpr () "[{ssl, true}, {port, 443}]" + let sslKey = Erlang.binaryToAtom "ssl" + let missingKey = Erlang.binaryToAtom "missing" + assertThat (proplists.is_defined (sslKey, pl)) (isEqualTo true) + assertThat (proplists.is_defined (missingKey, pl)) (isEqualTo false)) -[] -let ``test proplists.delete removes all entries with key`` () = -#if FABLE_COMPILER - let pl: BeamList = emitErlExpr () "[{a, 1}, {b, 2}, {a, 3}]" - let aKey = Erlang.binaryToAtom "a" - let bKey = Erlang.binaryToAtom "b" - let result = proplists.delete (aKey, pl) - proplists.is_defined (aKey, result) |> equal false - proplists.is_defined (bKey, result) |> equal true -#else - () -#endif + test ("delete removes all entries with key", fun _ -> + let pl: BeamList = emitErlExpr () "[{a, 1}, {b, 2}, {a, 3}]" + let aKey = Erlang.binaryToAtom "a" + let bKey = Erlang.binaryToAtom "b" + let result = proplists.delete (aKey, pl) + assertThat (proplists.is_defined (aKey, result)) (isEqualTo false) + assertThat (proplists.is_defined (bKey, result)) (isEqualTo true)) -[] -let ``test proplists.get_all_values returns all values for key`` () = -#if FABLE_COMPILER - let pl: BeamList = emitErlExpr () "[{x, 1}, {y, 2}, {x, 3}]" - let xKey = Erlang.binaryToAtom "x" - let vs: BeamList = proplists.get_all_values (xKey, pl) - let expected: BeamList = emitErlExpr () "[1, 3]" - vs |> equal expected -#else - () -#endif + test ("get_all_values returns all values for key", fun _ -> + let pl: BeamList = emitErlExpr () "[{x, 1}, {y, 2}, {x, 3}]" + let xKey = Erlang.binaryToAtom "x" + let vs: BeamList = proplists.get_all_values (xKey, pl) + let expected: BeamList = emitErlExpr () "[1, 3]" + assertThat vs (isEqualTo expected)) -[] -let ``test proplists.to_map converts proplist to map`` () = -#if FABLE_COMPILER - let pl: BeamList = emitErlExpr () "[{a, 1}, {b, 2}]" - let m: BeamMap = proplists.to_map pl - let aKey = Erlang.binaryToAtom "a" - let bKey = Erlang.binaryToAtom "b" - maps.get (aKey, m) |> equal 1 - maps.get (bKey, m) |> equal 2 -#else - () -#endif + test ("to_map converts proplist to map", fun _ -> + let pl: BeamList = emitErlExpr () "[{a, 1}, {b, 2}]" + let m: BeamMap = proplists.to_map pl + let aKey = Erlang.binaryToAtom "a" + let bKey = Erlang.binaryToAtom "b" + assertThat (maps.get (aKey, m)) (isEqualTo 1) + assertThat (maps.get (bKey, m)) (isEqualTo 2)) -[] -let ``test proplists.unfold expands bare atoms to {Atom, true}`` () = -#if FABLE_COMPILER - // [ssl, {port, 443}] -> [{ssl, true}, {port, 443}] - let pl: BeamList = emitErlExpr () "[ssl, {port, 443}]" - let result = proplists.unfold pl - let expected: BeamList = emitErlExpr () "[{ssl, true}, {port, 443}]" - result |> equal expected -#else - () -#endif + test ("unfold expands bare atoms to {Atom, true}", fun _ -> + // [ssl, {port, 443}] -> [{ssl, true}, {port, 443}] + let pl: BeamList = emitErlExpr () "[ssl, {port, 443}]" + let result = proplists.unfold pl + let expected: BeamList = emitErlExpr () "[{ssl, true}, {port, 443}]" + assertThat result (isEqualTo expected)) -[] -let ``test proplists.compact collapses {Atom, true} to bare atoms`` () = -#if FABLE_COMPILER - // [{ssl, true}, {port, 443}] -> [ssl, {port, 443}] - let pl: BeamList = emitErlExpr () "[{ssl, true}, {port, 443}]" - let result = proplists.compact pl - let expected: BeamList = emitErlExpr () "[ssl, {port, 443}]" - result |> equal expected -#else - () -#endif + test ("compact collapses {Atom, true} to bare atoms", fun _ -> + // [{ssl, true}, {port, 443}] -> [ssl, {port, 443}] + let pl: BeamList = emitErlExpr () "[{ssl, true}, {port, 443}]" + let result = proplists.compact pl + let expected: BeamList = emitErlExpr () "[ssl, {port, 443}]" + assertThat result (isEqualTo expected)) -[] -let ``test proplists.get_keys returns deduplicated keys`` () = -#if FABLE_COMPILER - // [{a, 1}, {b, 2}, {a, 3}] -> [a, b] (unordered, no duplicates) - let pl: BeamList = emitErlExpr () "[{a, 1}, {b, 2}, {a, 3}]" - let ks: Atom array = proplists.get_keys pl - ks |> Array.length |> equal 2 - let aKey = Erlang.binaryToAtom "a" - let bKey = Erlang.binaryToAtom "b" - ks |> Array.contains aKey |> equal true - ks |> Array.contains bKey |> equal true -#else - () -#endif + test ("get_keys returns deduplicated keys", fun _ -> + // [{a, 1}, {b, 2}, {a, 3}] -> [a, b] (unordered, no duplicates) + let pl: BeamList = emitErlExpr () "[{a, 1}, {b, 2}, {a, 3}]" + let ks: Atom array = proplists.get_keys pl + assertThat (ks |> Array.length) (isEqualTo 2) + let aKey = Erlang.binaryToAtom "a" + let bKey = Erlang.binaryToAtom "b" + assertThat (ks |> Array.contains aKey) (isTrue) + assertThat (ks |> Array.contains bKey) (isTrue)) -[] -let ``test proplists.from_map converts map to proplist`` () = -#if FABLE_COMPILER - let m: BeamMap = maps.new_ () - let aKey = Erlang.binaryToAtom "a" - let bKey = Erlang.binaryToAtom "b" - let m = maps.put (aKey, 1, m) - let m = maps.put (bKey, 2, m) - let pl: BeamList = proplists.from_map m - proplists.is_defined (aKey, pl) |> equal true - proplists.is_defined (bKey, pl) |> equal true - let aVal: int option = proplists.get_value (aKey, pl) - aVal |> equal (Some 1) -#else - () -#endif + test ("from_map converts map to proplist", fun _ -> + let m: BeamMap = maps.new_ () + let aKey = Erlang.binaryToAtom "a" + let bKey = Erlang.binaryToAtom "b" + let m = maps.put (aKey, 1, m) + let m = maps.put (bKey, 2, m) + let pl: BeamList = proplists.from_map m + assertThat (proplists.is_defined (aKey, pl)) (isEqualTo true) + assertThat (proplists.is_defined (bKey, pl)) (isEqualTo true) + let aVal: int option = proplists.get_value (aKey, pl) + assertThat aVal (isEqualTo (Some 1))) ] + ) diff --git a/test/TestQueue.fs b/test/TestQueue.fs index e397157..fae540d 100644 --- a/test/TestQueue.fs +++ b/test/TestQueue.fs @@ -1,298 +1,167 @@ module Fable.Beam.Tests.Queue -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.Queue -#endif -[] -let ``test new creates empty queue`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - queue.is_empty q |> equal true -#else - () -#endif - -[] -let ``test is_queue returns true for queue`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - queue.is_queue q |> equal true -#else - () -#endif - -[] -let ``test is_queue returns false for non-queue`` () = -#if FABLE_COMPILER - queue.is_queue (box 42) |> equal false -#else - () -#endif - -[] -let ``test len returns zero for empty queue`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - queue.len q |> equal 0 -#else - () -#endif - -[] -let ``test in adds element at rear`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let q1 = queue.``in`` (1, q) - let q2 = queue.``in`` (2, q1) - queue.len q2 |> equal 2 -#else - () -#endif - -[] -let ``test in_r adds element at front`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let q1 = queue.``in`` (1, q) - let q2 = queue.in_r (99, q1) - queue.head q2 |> equal 99 -#else - () -#endif - -[] -let ``test head returns front element`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let q1 = queue.``in`` (10, q) - let q2 = queue.``in`` (20, q1) - queue.head q2 |> equal 10 -#else - () -#endif - -[] -let ``test last returns rear element`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let q1 = queue.``in`` (10, q) - let q2 = queue.``in`` (20, q1) - queue.last q2 |> equal 20 -#else - () -#endif - -[] -let ``test tail removes front element`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let q1 = queue.``in`` (1, q) - let q2 = queue.``in`` (2, q1) - let q3 = queue.tail q2 - queue.len q3 |> equal 1 - queue.head q3 |> equal 2 -#else - () -#endif - -[] -let ``test init removes rear element`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let q1 = queue.``in`` (1, q) - let q2 = queue.``in`` (2, q1) - let q3 = queue.init q2 - queue.len q3 |> equal 1 - queue.last q3 |> equal 1 -#else - () -#endif - -[] -let ``test to_list returns elements front first`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let q1 = queue.``in`` (1, q) - let q2 = queue.``in`` (2, q1) - let q3 = queue.``in`` (3, q2) - queue.to_list q3 |> equal [ 1; 2; 3 ] -#else - () -#endif - -[] -let ``test from_list builds queue from list`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 1; 2; 3 ] - queue.len q |> equal 3 - queue.head q |> equal 1 - queue.last q |> equal 3 -#else - () -#endif - -[] -let ``test member returns true when element present`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 1; 2; 3 ] - queue.``member`` (2, q) |> equal true -#else - () -#endif - -[] -let ``test member returns false when element absent`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 1; 2; 3 ] - queue.``member`` (99, q) |> equal false -#else - () -#endif - -[] -let ``test reverse reverses order`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 1; 2; 3 ] - let r = queue.reverse q - queue.to_list r |> equal [ 3; 2; 1 ] -#else - () -#endif - -[] -let ``test join appends two queues`` () = -#if FABLE_COMPILER - let q1 = queue.from_list [ 1; 2 ] - let q2 = queue.from_list [ 3; 4 ] - let q3 = queue.join (q1, q2) - queue.to_list q3 |> equal [ 1; 2; 3; 4 ] -#else - () -#endif - -[] -let ``test filter keeps matching elements`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 1; 2; 3; 4; 5 ] - let evens = queue.filter ((fun x -> x % 2 = 0), q) - queue.to_list evens |> equal [ 2; 4 ] -#else - () -#endif - -[] -let ``test out removes front element`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 10; 20; 30 ] - let (item, q2) = out q - item |> equal (Some 10) - queue.len q2 |> equal 2 -#else - () -#endif - -[] -let ``test out returns None for empty queue`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let (item, _) = out q - item |> equal None -#else - () -#endif - -[] -let ``test outRear removes rear element`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 10; 20; 30 ] - let (item, q2) = outRear q - item |> equal (Some 30) - queue.len q2 |> equal 2 -#else - () -#endif - -[] -let ``test outRear returns None for empty queue`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - let (item, _) = outRear q - item |> equal None -#else - () -#endif - -[] -let ``test peek returns front element without removing`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 10; 20 ] - peek q |> equal (Some 10) - queue.len q |> equal 2 -#else - () -#endif - -[] -let ``test peek returns None for empty queue`` () = -#if FABLE_COMPILER - let q = queue.``new`` () - peek q |> equal None -#else - () -#endif - -[] -let ``test peekRear returns rear element without removing`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 10; 20; 30 ] - peekRear q |> equal (Some 30) - queue.len q |> equal 3 -#else - () -#endif - -[] -let ``test split divides queue at position`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 1; 2; 3; 4; 5 ] - let (q1, q2) = split 3 q - queue.to_list q1 |> equal [ 1; 2; 3 ] - queue.to_list q2 |> equal [ 4; 5 ] -#else - () -#endif - -[] -let ``test split at zero yields empty front`` () = -#if FABLE_COMPILER - let q = queue.from_list [ 1; 2; 3 ] - let (q1, q2) = split 0 q - queue.is_empty q1 |> equal true - queue.to_list q2 |> equal [ 1; 2; 3 ] -#else - () -#endif - -[] -let ``test fifo ordering is preserved`` () = -#if FABLE_COMPILER - // Enqueue 1, 2, 3 — dequeue should yield 1, 2, 3 - let q0 = queue.``new`` () - let q1 = queue.``in`` (1, q0) - let q2 = queue.``in`` (2, q1) - let q3 = queue.``in`` (3, q2) - let (a, q4) = out q3 - let (b, q5) = out q4 - let (c, _) = out q5 - a |> equal (Some 1) - b |> equal (Some 2) - c |> equal (Some 3) -#else - () -#endif +let tests = + testList ( + "Queue", + [ test ("new creates empty queue", fun _ -> + let q = queue.``new`` () + assertThat (queue.is_empty q) (isTrue)) + + test ("is_queue returns true for queue", fun _ -> + let q = queue.``new`` () + assertThat (queue.is_queue q) (isTrue)) + + test ("is_queue returns false for non-queue", fun _ -> + assertThat (queue.is_queue (box 42)) (isFalse)) + + test ("len returns zero for empty queue", fun _ -> + let q = queue.``new`` () + assertThat (queue.len q) (isEqualTo 0)) + + test ("in adds element at rear", fun _ -> + let q = queue.``new`` () + let q1 = queue.``in`` (1, q) + let q2 = queue.``in`` (2, q1) + assertThat (queue.len q2) (isEqualTo 2)) + + test ("in_r adds element at front", fun _ -> + let q = queue.``new`` () + let q1 = queue.``in`` (1, q) + let q2 = queue.in_r (99, q1) + assertThat (queue.head q2) (isEqualTo 99)) + + test ("head returns front element", fun _ -> + let q = queue.``new`` () + let q1 = queue.``in`` (10, q) + let q2 = queue.``in`` (20, q1) + assertThat (queue.head q2) (isEqualTo 10)) + + test ("last returns rear element", fun _ -> + let q = queue.``new`` () + let q1 = queue.``in`` (10, q) + let q2 = queue.``in`` (20, q1) + assertThat (queue.last q2) (isEqualTo 20)) + + test ("tail removes front element", fun _ -> + let q = queue.``new`` () + let q1 = queue.``in`` (1, q) + let q2 = queue.``in`` (2, q1) + let q3 = queue.tail q2 + assertThat (queue.len q3) (isEqualTo 1) + assertThat (queue.head q3) (isEqualTo 2)) + + test ("init removes rear element", fun _ -> + let q = queue.``new`` () + let q1 = queue.``in`` (1, q) + let q2 = queue.``in`` (2, q1) + let q3 = queue.init q2 + assertThat (queue.len q3) (isEqualTo 1) + assertThat (queue.last q3) (isEqualTo 1)) + + test ("to_list returns elements front first", fun _ -> + let q = queue.``new`` () + let q1 = queue.``in`` (1, q) + let q2 = queue.``in`` (2, q1) + let q3 = queue.``in`` (3, q2) + assertThat (queue.to_list q3) (isEqualTo [ 1; 2; 3 ])) + + test ("from_list builds queue from list", fun _ -> + let q = queue.from_list [ 1; 2; 3 ] + assertThat (queue.len q) (isEqualTo 3) + assertThat (queue.head q) (isEqualTo 1) + assertThat (queue.last q) (isEqualTo 3)) + + test ("member returns true when element present", fun _ -> + let q = queue.from_list [ 1; 2; 3 ] + assertThat (queue.``member`` (2, q)) (isTrue)) + + test ("member returns false when element absent", fun _ -> + let q = queue.from_list [ 1; 2; 3 ] + assertThat (queue.``member`` (99, q)) (isFalse)) + + test ("reverse reverses order", fun _ -> + let q = queue.from_list [ 1; 2; 3 ] + let r = queue.reverse q + assertThat (queue.to_list r) (isEqualTo [ 3; 2; 1 ])) + + test ("join appends two queues", fun _ -> + let q1 = queue.from_list [ 1; 2 ] + let q2 = queue.from_list [ 3; 4 ] + let q3 = queue.join (q1, q2) + assertThat (queue.to_list q3) (isEqualTo [ 1; 2; 3; 4 ])) + + test ("filter keeps matching elements", fun _ -> + let q = queue.from_list [ 1; 2; 3; 4; 5 ] + let evens = queue.filter ((fun x -> x % 2 = 0), q) + assertThat (queue.to_list evens) (isEqualTo [ 2; 4 ])) + + test ("out removes front element", fun _ -> + let q = queue.from_list [ 10; 20; 30 ] + let (item, q2) = out q + assertThat item (isEqualTo (Some 10)) + assertThat (queue.len q2) (isEqualTo 2)) + + test ("out returns None for empty queue", fun _ -> + let q = queue.``new`` () + let (item, _) = out q + assertThat item (isEqualTo None)) + + test ("outRear removes rear element", fun _ -> + let q = queue.from_list [ 10; 20; 30 ] + let (item, q2) = outRear q + assertThat item (isEqualTo (Some 30)) + assertThat (queue.len q2) (isEqualTo 2)) + + test ("outRear returns None for empty queue", fun _ -> + let q = queue.``new`` () + let (item, _) = outRear q + assertThat item (isEqualTo None)) + + test ("peek returns front element without removing", fun _ -> + let q = queue.from_list [ 10; 20 ] + assertThat (peek q) (isEqualTo (Some 10)) + assertThat (queue.len q) (isEqualTo 2)) + + test ("peek returns None for empty queue", fun _ -> + let q = queue.``new`` () + assertThat (peek q) (isEqualTo None)) + + test ("peekRear returns rear element without removing", fun _ -> + let q = queue.from_list [ 10; 20; 30 ] + assertThat (peekRear q) (isEqualTo (Some 30)) + assertThat (queue.len q) (isEqualTo 3)) + + test ("split divides queue at position", fun _ -> + let q = queue.from_list [ 1; 2; 3; 4; 5 ] + let (q1, q2) = split 3 q + assertThat (queue.to_list q1) (isEqualTo [ 1; 2; 3 ]) + assertThat (queue.to_list q2) (isEqualTo [ 4; 5 ])) + + test ("split at zero yields empty front", fun _ -> + let q = queue.from_list [ 1; 2; 3 ] + let (q1, q2) = split 0 q + assertThat (queue.is_empty q1) (isTrue) + assertThat (queue.to_list q2) (isEqualTo [ 1; 2; 3 ])) + + test ("fifo ordering is preserved", fun _ -> + // Enqueue 1, 2, 3 — dequeue should yield 1, 2, 3 + let q0 = queue.``new`` () + let q1 = queue.``in`` (1, q0) + let q2 = queue.``in`` (2, q1) + let q3 = queue.``in`` (3, q2) + let (a, q4) = out q3 + let (b, q5) = out q4 + let (c, _) = out q5 + assertThat a (isEqualTo (Some 1)) + assertThat b (isEqualTo (Some 2)) + assertThat c (isEqualTo (Some 3))) ] + ) diff --git a/test/TestRand.fs b/test/TestRand.fs index db48509..5f0ee93 100644 --- a/test/TestRand.fs +++ b/test/TestRand.fs @@ -1,112 +1,65 @@ module Fable.Beam.Tests.Rand -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.Rand -#endif -[] -let ``test rand.uniform returns float in range`` () = -#if FABLE_COMPILER - let v = rand.uniform () - (v >= 0.0 && v < 1.0) |> equal true -#else - () -#endif +let tests = + testList ( + "Rand", + [ test ("uniform returns float in range", fun _ -> + let v = rand.uniform () + assertThat (v >= 0.0 && v < 1.0) (isTrue)) -[] -let ``test rand.seed with typed algorithm DU`` () = -#if FABLE_COMPILER - // Canary for StringEnum-style atom emission from F# DUs on BEAM. - // If DU case Exsss compiles to atom `exsss`, rand:seed/1 will accept it. - rand.seed Exsss |> ignore - let v = rand.uniform () - (v >= 0.0 && v < 1.0) |> equal true -#else - () -#endif + test ("seed with typed algorithm DU", fun _ -> + // Canary for StringEnum-style atom emission from F# DUs on BEAM. + // If DU case Exsss compiles to atom `exsss`, rand:seed/1 will accept it. + rand.seed Exsss |> ignore + let v = rand.uniform () + assertThat (v >= 0.0 && v < 1.0) (isTrue)) -[] -let ``test rand.seed multi-word DU case maps to atom`` () = -#if FABLE_COMPILER - // Second canary: does a multi-word case like Exro928ss produce atom exro928ss? - rand.seed Exro928ss |> ignore - let v = rand.uniform () - (v >= 0.0 && v < 1.0) |> equal true -#else - () -#endif + test ("seed multi-word DU case maps to atom", fun _ -> + // Second canary: does a multi-word case like Exro928ss produce atom exro928ss? + rand.seed Exro928ss |> ignore + let v = rand.uniform () + assertThat (v >= 0.0 && v < 1.0) (isTrue)) + test ("uniform n returns int in range", fun _ -> + let v = rand.uniform 100 + assertThat (v >= 1 && v <= 100) (isTrue)) -[] -let ``test rand.uniform n returns int in range`` () = -#if FABLE_COMPILER - let v = rand.uniform 100 - (v >= 1 && v <= 100) |> equal true -#else - () -#endif + test ("uniform 1 always returns 1", fun _ -> assertThat (rand.uniform 1) (isEqualTo 1)) -[] -let ``test rand.uniform 1 always returns 1`` () = -#if FABLE_COMPILER - rand.uniform 1 |> equal 1 -#else - () -#endif + test ("uniform_real returns positive float", fun _ -> + let v = rand.uniform_real () + assertThat (v > 0.0 && v < 1.0) (isTrue)) -[] -let ``test rand.uniform_real returns positive float`` () = -#if FABLE_COMPILER - let v = rand.uniform_real () - (v > 0.0 && v < 1.0) |> equal true -#else - () -#endif + test ("bytes returns binary of correct length", fun _ -> + let bytes = rand.bytes 16 + // The Erlang byte_size of the returned binary should be 16 + assertThat (bytes.Length > 0) (isTrue)) -[] -let ``test rand.bytes returns binary of correct length`` () = -#if FABLE_COMPILER - let bytes = rand.bytes 16 - // The Erlang byte_size of the returned binary should be 16 - (bytes.Length > 0) |> equal true -#else - () -#endif + test ("normal returns a float", fun _ -> + let v = rand.normal () + // Normal distribution — just check it's a finite float + assertThat (v = v) (isTrue) // NaN check: NaN <> NaN + ) -[] -let ``test rand.normal returns a float`` () = -#if FABLE_COMPILER - let v = rand.normal () - // Normal distribution — just check it's a finite float - (v = v) |> equal true // NaN check: NaN <> NaN -#else - () -#endif + test ("normal with mean and variance", fun _ -> + // With large variance we get varied values; just check it's a float + let v = rand.normal (0.0, 1.0) + assertThat (v = v) (isTrue)) -[] -let ``test rand.normal with mean and variance`` () = -#if FABLE_COMPILER - // With large variance we get varied values; just check it's a float - let v = rand.normal (0.0, 1.0) - (v = v) |> equal true -#else - () -#endif - -[] -let ``test two rand.uniform calls can differ`` () = -#if FABLE_COMPILER - // With N=1000000, getting the same value twice in a row is astronomically unlikely - let v1 = rand.uniform 1000000 - let v2 = rand.uniform 1000000 - // At least verify both are in range — equality would be a fluke - (v1 >= 1 && v1 <= 1000000) |> equal true - (v2 >= 1 && v2 <= 1000000) |> equal true -#else - () -#endif + test ("two uniform calls can differ", fun _ -> + // With N=1000000, getting the same value twice in a row is astronomically unlikely + let v1 = rand.uniform 1000000 + let v2 = rand.uniform 1000000 + // At least verify both are in range — equality would be a fluke + assertThat (v1 >= 1 && v1 <= 1000000) (isTrue) + assertThat (v2 >= 1 && v2 <= 1000000) (isTrue)) ] + ) diff --git a/test/TestRe.fs b/test/TestRe.fs index b23fa71..f77cb6a 100644 --- a/test/TestRe.fs +++ b/test/TestRe.fs @@ -1,340 +1,194 @@ module Fable.Beam.Tests.Re -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.Re open Fable.Beam.Lists -#endif - -[] -let ``test re.isMatch returns true for matching pattern`` () = -#if FABLE_COMPILER - isMatch "hello world" "hello" |> equal true -#else - () -#endif - -[] -let ``test re.isMatch returns false for non-matching pattern`` () = -#if FABLE_COMPILER - isMatch "hello world" "xyz" |> equal false -#else - () -#endif - -[] -let ``test re.isMatch with digit pattern`` () = -#if FABLE_COMPILER - isMatch "abc123" "\\d+" |> equal true -#else - () -#endif - -[] -let ``test re.isMatch anchored no match`` () = -#if FABLE_COMPILER - isMatch "hello" "^world" |> equal false -#else - () -#endif - -[] -let ``test re.compile returns Ok for valid pattern`` () = -#if FABLE_COMPILER - match compile "hello" with - | Ok _ -> true |> equal true - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test re.compile returns Error for invalid pattern`` () = -#if FABLE_COMPILER - match compile "[invalid" with - | Ok _ -> false |> equal true - | Error msg -> (msg.Length > 0) |> equal true -#else - () -#endif - -[] -let ``test re.isMatchMP with compiled pattern`` () = -#if FABLE_COMPILER - match compile "\\d+" with - | Ok mp -> isMatchMP "abc123" mp |> equal true - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test re.isMatchMP with compiled pattern no match`` () = -#if FABLE_COMPILER - match compile "\\d+" with - | Ok mp -> isMatchMP "abcdef" mp |> equal false - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test re.run returns Some with whole match at index 0`` () = -#if FABLE_COMPILER - match run "hello world" "hello" with - | Some captures -> captures.[0] |> equal "hello" - | None -> false |> equal true -#else - () -#endif - -[] -let ``test re.run returns Some with capture groups`` () = -#if FABLE_COMPILER - match run "hello world" "h(e)(l+)o" with - | Some captures -> - captures.[0] |> equal "hello" - captures.[1] |> equal "e" - captures.[2] |> equal "ll" - | None -> false |> equal true -#else - () -#endif - -[] -let ``test re.run returns None for no match`` () = -#if FABLE_COMPILER - run "hello world" "xyz" |> equal None -#else - () -#endif - -[] -let ``test re.runMP returns captures for compiled pattern`` () = -#if FABLE_COMPILER - match compile "(\\d+)" with - | Ok mp -> - match runMP "abc123def" mp with - | Some captures -> - captures.[0] |> equal "123" - captures.[1] |> equal "123" - | None -> false |> equal true - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test re.replaceFirst replaces only first occurrence`` () = -#if FABLE_COMPILER - replaceFirst "aabbaa" "a+" "X" |> equal "Xbbaa" -#else - () -#endif - -[] -let ``test re.replaceFirstWith caseless replaces first case-insensitively`` () = -#if FABLE_COMPILER - replaceFirstWith "Aabbaa" "a+" "X" [ caseless ] |> equal "Xbbaa" -#else - () -#endif - -[] -let ``test re.replaceAll replaces all occurrences`` () = -#if FABLE_COMPILER - replaceAll "aabbaa" "a+" "X" |> equal "XbbX" -#else - () -#endif - -[] -let ``test re.replaceAll with digit pattern`` () = -#if FABLE_COMPILER - replaceAll "abc123def456" "\\d+" "N" |> equal "abcNdefN" -#else - () -#endif - -[] -let ``test re.split on comma`` () = -#if FABLE_COMPILER - let parts = split "one,two,three" "," - parts.[0] |> equal "one" - parts.[1] |> equal "two" - parts.[2] |> equal "three" -#else - () -#endif - -[] -let ``test re.split on whitespace pattern`` () = -#if FABLE_COMPILER - let parts = split "a b c" "\\s+" - parts.[0] |> equal "a" - parts.[1] |> equal "b" - parts.[2] |> equal "c" -#else - () -#endif - -[] -let ``test re.splitParts limits result count`` () = -#if FABLE_COMPILER - let parts = splitParts "one,two,three,four" "," 2 - parts.[0] |> equal "one" - parts.[1] |> equal "two,three,four" -#else - () -#endif - -// ============================================================================ -// Options -// ============================================================================ - -[] -let ``test re.isMatchWith caseless option matches different case`` () = -#if FABLE_COMPILER - isMatchWith "HELLO" "hello" [ caseless ] |> equal true - // Sanity check: default is case-sensitive - isMatch "HELLO" "hello" |> equal false -#else - () -#endif - -[] -let ``test re.isMatchWith multiline option matches after newline`` () = -#if FABLE_COMPILER - // ^world only matches at line starts in multiline mode - isMatchWith "hello\nworld" "^world" [ multiline ] |> equal true - isMatch "hello\nworld" "^world" |> equal false -#else - () -#endif - -[] -let ``test re.isMatchWith unicode option handles multi-byte characters`` () = -#if FABLE_COMPILER - // "é" is 2 bytes in UTF-8. Without unicode, ^.$ expects exactly 1 byte — no match. - // With unicode, ^.$ expects exactly 1 codepoint — matches. - isMatch "é" "^.$" |> equal false - isMatchWith "é" "^.$" [ unicode ] |> equal true -#else - () -#endif - -[] -let ``test re.runWith caseless returns original-case captures`` () = -#if FABLE_COMPILER - match runWith "HELLO world" "hello" [ caseless ] with - | Some captures -> captures.[0] |> equal "HELLO" - | None -> false |> equal true -#else - () -#endif - -[] -let ``test re.compileWith caseless produces case-insensitive MP`` () = -#if FABLE_COMPILER - match compileWith "hello" [ caseless ] with - | Ok mp -> - isMatchMP "HELLO" mp |> equal true - isMatchMP "hello" mp |> equal true - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test re.replaceAllWith caseless replaces all cases`` () = -#if FABLE_COMPILER - replaceAllWith "Hello HELLO hello" "hello" "X" [ caseless ] |> equal "X X X" -#else - () -#endif - -[] -let ``test re.splitWith caseless splits on either case`` () = -#if FABLE_COMPILER - let parts = splitWith "aXbxc" "x" [ caseless ] - parts.[0] |> equal "a" - parts.[1] |> equal "b" - parts.[2] |> equal "c" -#else - () -#endif - -// ============================================================================ -// Compiled-pattern reuse (replace / split) -// ============================================================================ - -[] -let ``test re.replaceFirstMP with compiled pattern`` () = -#if FABLE_COMPILER - match compile "a+" with - | Ok mp -> replaceFirstMP "aabbaa" mp "X" |> equal "Xbbaa" - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test re.replaceAllMP with compiled pattern`` () = -#if FABLE_COMPILER - match compile "\\d+" with - | Ok mp -> replaceAllMP "abc123def456" mp "N" |> equal "abcNdefN" - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test re.splitMP with compiled pattern`` () = -#if FABLE_COMPILER - match compile "," with - | Ok mp -> - let parts = splitMP "one,two,three" mp - parts.[0] |> equal "one" - parts.[1] |> equal "two" - parts.[2] |> equal "three" - | Error _ -> false |> equal true -#else - () -#endif - -// ============================================================================ -// Edge cases -// ============================================================================ - -[] -let ``test re.isMatch with empty subject`` () = -#if FABLE_COMPILER - // Empty pattern matches empty subject (zero-width match at position 0) - isMatch "" "" |> equal true - isMatch "" "a" |> equal false -#else - () -#endif - -[] -let ``test re.run with empty subject and optional group returns Some empty`` () = -#if FABLE_COMPILER - match run "" "a*" with - | Some captures -> captures.[0] |> equal "" - | None -> false |> equal true -#else - () -#endif - -[] -let ``test re.splitRaw returns the native list form of split`` () = -#if FABLE_COMPILER - let parts: BeamList = splitRaw "a1b2c" "[0-9]" - let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b\">>, <<\"c\">>]" - parts |> equal expected -#else - () -#endif + +let tests = + testList ( + "Re", + [ test ("isMatch returns true for matching pattern", fun _ -> + assertThat (isMatch "hello world" "hello") (isTrue)) + + test ("isMatch returns false for non-matching pattern", fun _ -> + assertThat (isMatch "hello world" "xyz") (isFalse)) + + test ("isMatch with digit pattern", fun _ -> + assertThat (isMatch "abc123" "\\d+") (isTrue)) + + test ("isMatch anchored no match", fun _ -> + assertThat (isMatch "hello" "^world") (isFalse)) + + test ("compile returns Ok for valid pattern", fun _ -> + match compile "hello" with + | Ok _ -> assertThat true (isTrue) + | Error _ -> failwith "expected compile to succeed" + ) + + test ("compile returns Error for invalid pattern", fun _ -> + match compile "[invalid" with + | Ok _ -> failwith "expected compile to fail" + | Error msg -> assertThat (msg.Length > 0) (isTrue) + ) + + test ("isMatchMP with compiled pattern", fun _ -> + match compile "\\d+" with + | Ok mp -> assertThat (isMatchMP "abc123" mp) (isTrue) + | Error _ -> failwith "expected compile to succeed" + ) + + test ("isMatchMP with compiled pattern no match", fun _ -> + match compile "\\d+" with + | Ok mp -> assertThat (isMatchMP "abcdef" mp) (isFalse) + | Error _ -> failwith "expected compile to succeed" + ) + + test ("run returns Some with whole match at index 0", fun _ -> + match run "hello world" "hello" with + | Some captures -> assertThat captures.[0] (isEqualTo "hello") + | None -> failwith "expected a match" + ) + + test ("run returns Some with capture groups", fun _ -> + match run "hello world" "h(e)(l+)o" with + | Some captures -> + assertThat captures.[0] (isEqualTo "hello") + assertThat captures.[1] (isEqualTo "e") + assertThat captures.[2] (isEqualTo "ll") + | None -> failwith "expected a match" + ) + + test ("run returns None for no match", fun _ -> + assertThat (run "hello world" "xyz") (isEqualTo None)) + + test ("runMP returns captures for compiled pattern", fun _ -> + match compile "(\\d+)" with + | Ok mp -> + match runMP "abc123def" mp with + | Some captures -> + assertThat captures.[0] (isEqualTo "123") + assertThat captures.[1] (isEqualTo "123") + | None -> failwith "expected a capture" + | Error _ -> failwith "expected compile to succeed" + ) + + test ("replaceFirst replaces only first occurrence", fun _ -> + assertThat (replaceFirst "aabbaa" "a+" "X") (isEqualTo "Xbbaa")) + + test ("replaceFirstWith caseless replaces first case-insensitively", fun _ -> + assertThat (replaceFirstWith "Aabbaa" "a+" "X" [ caseless ]) (isEqualTo "Xbbaa")) + + test ("replaceAll replaces all occurrences", fun _ -> + assertThat (replaceAll "aabbaa" "a+" "X") (isEqualTo "XbbX")) + + test ("replaceAll with digit pattern", fun _ -> + assertThat (replaceAll "abc123def456" "\\d+" "N") (isEqualTo "abcNdefN")) + + test ("split on comma", fun _ -> + let parts = split "one,two,three" "," + assertThat parts.[0] (isEqualTo "one") + assertThat parts.[1] (isEqualTo "two") + assertThat parts.[2] (isEqualTo "three") + ) + + test ("split on whitespace pattern", fun _ -> + let parts = split "a b c" "\\s+" + assertThat parts.[0] (isEqualTo "a") + assertThat parts.[1] (isEqualTo "b") + assertThat parts.[2] (isEqualTo "c") + ) + + test ("splitParts limits result count", fun _ -> + let parts = splitParts "one,two,three,four" "," 2 + assertThat parts.[0] (isEqualTo "one") + assertThat parts.[1] (isEqualTo "two,three,four") + ) + + test ("isMatchWith caseless option matches different case", fun _ -> + assertThat (isMatchWith "HELLO" "hello" [ caseless ]) (isTrue) + // Sanity check: default is case-sensitive + assertThat (isMatch "HELLO" "hello") (isFalse) + ) + + test ("isMatchWith multiline option matches after newline", fun _ -> + // ^world only matches at line starts in multiline mode + assertThat (isMatchWith "hello\nworld" "^world" [ multiline ]) (isTrue) + assertThat (isMatch "hello\nworld" "^world") (isFalse) + ) + + test ("isMatchWith unicode option handles multi-byte characters", fun _ -> + // "é" is 2 bytes in UTF-8. Without unicode, ^.$ expects exactly 1 byte — no match. + // With unicode, ^.$ expects exactly 1 codepoint — matches. + assertThat (isMatch "é" "^.$") (isFalse) + assertThat (isMatchWith "é" "^.$" [ unicode ]) (isTrue) + ) + + test ("runWith caseless returns original-case captures", fun _ -> + match runWith "HELLO world" "hello" [ caseless ] with + | Some captures -> assertThat captures.[0] (isEqualTo "HELLO") + | None -> failwith "expected a match" + ) + + test ("compileWith caseless produces case-insensitive MP", fun _ -> + match compileWith "hello" [ caseless ] with + | Ok mp -> + assertThat (isMatchMP "HELLO" mp) (isTrue) + assertThat (isMatchMP "hello" mp) (isTrue) + | Error _ -> failwith "expected compile to succeed" + ) + + test ("replaceAllWith caseless replaces all cases", fun _ -> + assertThat (replaceAllWith "Hello HELLO hello" "hello" "X" [ caseless ]) (isEqualTo "X X X")) + + test ("splitWith caseless splits on either case", fun _ -> + let parts = splitWith "aXbxc" "x" [ caseless ] + assertThat parts.[0] (isEqualTo "a") + assertThat parts.[1] (isEqualTo "b") + assertThat parts.[2] (isEqualTo "c") + ) + + test ("replaceFirstMP with compiled pattern", fun _ -> + match compile "a+" with + | Ok mp -> assertThat (replaceFirstMP "aabbaa" mp "X") (isEqualTo "Xbbaa") + | Error _ -> failwith "expected compile to succeed" + ) + + test ("replaceAllMP with compiled pattern", fun _ -> + match compile "\\d+" with + | Ok mp -> assertThat (replaceAllMP "abc123def456" mp "N") (isEqualTo "abcNdefN") + | Error _ -> failwith "expected compile to succeed" + ) + + test ("splitMP with compiled pattern", fun _ -> + match compile "," with + | Ok mp -> + let parts = splitMP "one,two,three" mp + assertThat parts.[0] (isEqualTo "one") + assertThat parts.[1] (isEqualTo "two") + assertThat parts.[2] (isEqualTo "three") + | Error _ -> failwith "expected compile to succeed" + ) + + test ("isMatch with empty subject", fun _ -> + // Empty pattern matches empty subject (zero-width match at position 0) + assertThat (isMatch "" "") (isTrue) + assertThat (isMatch "" "a") (isFalse) + ) + + test ("run with empty subject and optional group returns Some empty", fun _ -> + match run "" "a*" with + | Some captures -> assertThat captures.[0] (isEqualTo "") + | None -> failwith "expected a match" + ) + + test ("splitRaw returns the native list form of split", fun _ -> + let parts: BeamList = splitRaw "a1b2c" "[0-9]" + let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b\">>, <<\"c\">>]" + assertThat parts (isEqualTo expected) + ) ] + ) diff --git a/test/TestString.fs b/test/TestString.fs index 20c7946..61f8e79 100644 --- a/test/TestString.fs +++ b/test/TestString.fs @@ -1,134 +1,14 @@ module Fable.Beam.Tests.String -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam open Fable.Beam.String open Fable.Beam.Lists -#endif - -[] -let ``test str.is_empty returns true for empty`` () = -#if FABLE_COMPILER - str.is_empty "" |> equal true -#else - () -#endif - -[] -let ``test str.is_empty returns false for non-empty`` () = -#if FABLE_COMPILER - str.is_empty "hello" |> equal false -#else - () -#endif - -[] -let ``test str.length returns grapheme count`` () = -#if FABLE_COMPILER - str.length "hello" |> equal 5 -#else - () -#endif - -[] -let ``test str.lowercase converts to lowercase`` () = -#if FABLE_COMPILER - str.lowercase "HELLO" |> equal "hello" -#else - () -#endif - -[] -let ``test str.uppercase converts to uppercase`` () = -#if FABLE_COMPILER - str.uppercase "hello" |> equal "HELLO" -#else - () -#endif - -[] -let ``test str.titlecase capitalises first grapheme`` () = -#if FABLE_COMPILER - str.titlecase "hello world" |> equal "Hello world" -#else - () -#endif - -[] -let ``test str.casefold lowercases for comparison`` () = -#if FABLE_COMPILER - str.casefold "HELLO" |> equal "hello" -#else - () -#endif - -[] -let ``test str.reverse reverses string`` () = -#if FABLE_COMPILER - reverse "hello" |> equal "olleh" -#else - () -#endif - -[] -let ``test str.trim strips whitespace`` () = -#if FABLE_COMPILER - str.trim " hello " |> equal "hello" -#else - () -#endif - -[] -let ``test str.trim with leading direction`` () = -#if FABLE_COMPILER - let leading = Erlang.binaryToAtom "leading" - str.trim (" hello ", leading) |> equal "hello " -#else - () -#endif - -[] -let ``test str.trim with trailing direction`` () = -#if FABLE_COMPILER - let trailing = Erlang.binaryToAtom "trailing" - str.trim (" hello ", trailing) |> equal " hello" -#else - () -#endif - -[] -let ``test str.pad trailing to length`` () = -#if FABLE_COMPILER - pad "hi" 5 |> equal "hi " -#else - () -#endif - -[] -let ``test str.pad leading with direction`` () = -#if FABLE_COMPILER - let leading = Erlang.binaryToAtom "leading" - padDir "hi" 5 leading |> equal " hi" -#else - () -#endif - -[] -let ``test str.pad with custom character`` () = -#if FABLE_COMPILER - let leading = Erlang.binaryToAtom "leading" - padWith "7" 3 leading "0" |> equal "007" -#else - () -#endif - -// ---------------------------------------------------------------------------- -// Raw chardata variants (BeamChardata) -// ---------------------------------------------------------------------------- #if FABLE_COMPILER // The raw variants return unflattened chardata: an iolist/charlist, i.e. a *list*, never a binary. @@ -136,220 +16,166 @@ let ``test str.pad with custom character`` () = let private isList (x: BeamChardata) : bool = nativeOnly #endif -[] -let ``test padRaw returns unflattened chardata that flattens to pad`` () = -#if FABLE_COMPILER - let raw = padRaw "hi" 5 - // proves it is genuinely raw: string:pad yields an iolist ([<<"hi">>,32,32,32]), not a binary - isList raw |> equal true - BeamChardata.toString raw |> equal "hi " - BeamChardata.toString raw |> equal (pad "hi" 5) -#else - () -#endif - -[] -let ``test reverseRaw flattens back to reverse`` () = -#if FABLE_COMPILER - let raw = reverseRaw "hello" - isList raw |> equal true - BeamChardata.toString raw |> equal "olleh" -#else - () -#endif - -[] -let ``test replaceAllRaw flattens back to replaceAll`` () = -#if FABLE_COMPILER - let raw = replaceAllRaw "aXbXa" "X" "Y" - BeamChardata.toString raw |> equal "aYbYa" - BeamChardata.toString raw |> equal (replaceAll "aXbXa" "X" "Y") -#else - () -#endif +let tests = + testList ( + "String", + [ test ("is_empty returns true for empty", fun _ -> + assertThat (str.is_empty "") (isTrue)) -[] -let ``test BeamChardata ofString roundtrips through toString`` () = -#if FABLE_COMPILER - "hi" |> BeamChardata.ofString |> BeamChardata.toString |> equal "hi" -#else - () -#endif + test ("is_empty returns false for non-empty", fun _ -> + assertThat (str.is_empty "hello") (isFalse)) -[] -let ``test str.slice from position`` () = -#if FABLE_COMPILER - str.slice ("hello world", 6) |> equal "world" -#else - () -#endif + test ("length returns grapheme count", fun _ -> + assertThat (str.length "hello") (isEqualTo 5)) -[] -let ``test str.slice with length`` () = -#if FABLE_COMPILER - str.slice ("hello world", 0, 5) |> equal "hello" -#else - () -#endif - -[] -let ``test str.equal compares strings`` () = -#if FABLE_COMPILER - str.equal ("hello", "hello") |> equal true - str.equal ("hello", "world") |> equal false -#else - () -#endif + test ("lowercase converts to lowercase", fun _ -> + assertThat (str.lowercase "HELLO") (isEqualTo "hello")) -[] -let ``test str.equal case-insensitive`` () = -#if FABLE_COMPILER - str.equal ("Hello", "hello", true) |> equal true - str.equal ("Hello", "world", true) |> equal false -#else - () -#endif + test ("uppercase converts to uppercase", fun _ -> + assertThat (str.uppercase "hello") (isEqualTo "HELLO")) -[] -let ``test find returns Some on match`` () = -#if FABLE_COMPILER - find "hello world" "world" |> equal (Some "world") -#else - () -#endif + test ("titlecase capitalises first grapheme", fun _ -> + assertThat (str.titlecase "hello world") (isEqualTo "Hello world")) -[] -let ``test find returns None when not found`` () = -#if FABLE_COMPILER - find "hello world" "xyz" |> equal None -#else - () -#endif + test ("casefold lowercases for comparison", fun _ -> + assertThat (str.casefold "HELLO") (isEqualTo "hello")) -[] -let ``test findFrom trailing finds last occurrence`` () = -#if FABLE_COMPILER - let trailing = Erlang.binaryToAtom "trailing" - findFrom "a-b-c" "-" trailing |> equal (Some "-c") -#else - () -#endif + test ("reverse reverses string", fun _ -> + assertThat (reverse "hello") (isEqualTo "olleh")) -[] -let ``test prefix returns Some rest when prefix matches`` () = -#if FABLE_COMPILER - prefix "hello world" "hello " |> equal (Some "world") -#else - () -#endif + test ("trim strips whitespace", fun _ -> + assertThat (str.trim " hello ") (isEqualTo "hello")) -[] -let ``test prefix returns None when no match`` () = -#if FABLE_COMPILER - prefix "hello world" "xyz" |> equal None -#else - () -#endif + test ("trim with leading direction", fun _ -> + let leading = Erlang.binaryToAtom "leading" + assertThat (str.trim (" hello ", leading)) (isEqualTo "hello ") + ) -[] -let ``test splitFirst splits at first occurrence`` () = -#if FABLE_COMPILER - let parts = splitFirst "hello world" " " - Array.length parts |> equal 2 - parts.[0] |> equal "hello" - parts.[1] |> equal "world" -#else - () -#endif + test ("trim with trailing direction", fun _ -> + let trailing = Erlang.binaryToAtom "trailing" + assertThat (str.trim (" hello ", trailing)) (isEqualTo " hello") + ) -[] -let ``test splitAll splits at all occurrences`` () = -#if FABLE_COMPILER - let parts = splitAll "a,b,c" "," - Array.length parts |> equal 3 - parts.[0] |> equal "a" - parts.[1] |> equal "b" - parts.[2] |> equal "c" -#else - () -#endif - -[] -let ``test replaceFirst replaces first occurrence`` () = -#if FABLE_COMPILER - replaceFirst "aabbaa" "aa" "XX" |> equal "XXbbaa" -#else - () -#endif - -[] -let ``test replaceAll replaces all occurrences`` () = -#if FABLE_COMPILER - replaceAll "aabbaa" "aa" "XX" |> equal "XXbbXX" -#else - () -#endif - -[] -let ``test toInteger parses valid integer`` () = -#if FABLE_COMPILER - match toInteger "42abc" with - | Ok(n, rest) -> - n |> equal 42 - rest |> equal "abc" - | Error _ -> equal true false -#else - () -#endif - -[] -let ``test toInteger returns error for non-integer`` () = -#if FABLE_COMPILER - match toInteger "abc" with - | Error _ -> equal true true - | Ok _ -> equal true false -#else - () -#endif - -[] -let ``test toFloat parses valid float`` () = -#if FABLE_COMPILER - match toFloat "3.14rest" with - | Ok(f, _) -> (f > 3.13 && f < 3.15) |> equal true - | Error _ -> equal true false -#else - () -#endif - -[] -let ``test toGraphemes splits into grapheme clusters`` () = -#if FABLE_COMPILER - let graphemes = toGraphemes "abc" - Array.length graphemes |> equal 3 - graphemes.[0] |> equal "a" - graphemes.[1] |> equal "b" - graphemes.[2] |> equal "c" -#else - () -#endif - -[] -let ``test splitAllRaw returns the native list form of splitAll`` () = -#if FABLE_COMPILER - let parts: BeamList = splitAllRaw "a,b,c" "," - let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b\">>, <<\"c\">>]" - parts |> equal expected -#else - () -#endif - -[] -let ``test splitFirstRaw returns the native list form of splitFirst`` () = -#if FABLE_COMPILER - let parts: BeamList = splitFirstRaw "hello world" " " - let expected: BeamList = emitErlExpr () "[<<\"hello\">>, <<\"world\">>]" - parts |> equal expected -#else - () -#endif + test ("pad trailing to length", fun _ -> + assertThat (pad "hi" 5) (isEqualTo "hi ")) + + test ("pad leading with direction", fun _ -> + let leading = Erlang.binaryToAtom "leading" + assertThat (padDir "hi" 5 leading) (isEqualTo " hi")) + + test ("pad with custom character", fun _ -> + let leading = Erlang.binaryToAtom "leading" + assertThat (padWith "7" 3 leading "0") (isEqualTo "007")) + + test ("padRaw returns unflattened chardata that flattens to pad", fun _ -> + let raw = padRaw "hi" 5 + // proves it is genuinely raw: string:pad yields an iolist ([<<"hi">>,32,32,32]), not a binary + assertThat (isList raw) (isTrue) + assertThat (BeamChardata.toString raw) (isEqualTo "hi ") + assertThat (BeamChardata.toString raw) (isEqualTo (pad "hi" 5)) + ) + + test ("reverseRaw flattens back to reverse", fun _ -> + let raw = reverseRaw "hello" + assertThat (isList raw) (isTrue) + assertThat (BeamChardata.toString raw) (isEqualTo "olleh") + ) + + test ("replaceAllRaw flattens back to replaceAll", fun _ -> + let raw = replaceAllRaw "aXbXa" "X" "Y" + assertThat (BeamChardata.toString raw) (isEqualTo "aYbYa") + assertThat (BeamChardata.toString raw) (isEqualTo (replaceAll "aXbXa" "X" "Y")) + ) + + test ("BeamChardata ofString roundtrips through toString", fun _ -> + let result = "hi" |> BeamChardata.ofString |> BeamChardata.toString + assertThat result (isEqualTo "hi")) + + test ("slice from position", fun _ -> + assertThat (str.slice ("hello world", 6)) (isEqualTo "world")) + + test ("slice with length", fun _ -> + assertThat (str.slice ("hello world", 0, 5)) (isEqualTo "hello")) + + test ("equal compares strings", fun _ -> + assertThat (str.equal ("hello", "hello")) (isTrue) + assertThat (str.equal ("hello", "world")) (isFalse) + ) + + test ("equal case-insensitive", fun _ -> + assertThat (str.equal ("Hello", "hello", true)) (isTrue) + assertThat (str.equal ("Hello", "world", true)) (isFalse) + ) + + test ("find returns Some on match", fun _ -> + assertThat (find "hello world" "world") (isEqualTo (Some "world"))) + + test ("find returns None when not found", fun _ -> + assertThat (find "hello world" "xyz") (isEqualTo None)) + + test ("findFrom trailing finds last occurrence", fun _ -> + let trailing = Erlang.binaryToAtom "trailing" + assertThat (findFrom "a-b-c" "-" trailing) (isEqualTo (Some "-c"))) + + test ("prefix returns Some rest when prefix matches", fun _ -> + assertThat (prefix "hello world" "hello ") (isEqualTo (Some "world"))) + + test ("prefix returns None when no match", fun _ -> + assertThat (prefix "hello world" "xyz") (isEqualTo None)) + + test ("splitFirst splits at first occurrence", fun _ -> + let parts = splitFirst "hello world" " " + assertThat (Array.length parts) (isEqualTo 2) + assertThat (parts.[0]) (isEqualTo "hello") + assertThat (parts.[1]) (isEqualTo "world") + ) + + test ("splitAll splits at all occurrences", fun _ -> + let parts = splitAll "a,b,c" "," + assertThat (Array.length parts) (isEqualTo 3) + assertThat (parts.[0]) (isEqualTo "a") + assertThat (parts.[1]) (isEqualTo "b") + assertThat (parts.[2]) (isEqualTo "c") + ) + + test ("replaceFirst replaces first occurrence", fun _ -> + assertThat (replaceFirst "aabbaa" "aa" "XX") (isEqualTo "XXbbaa")) + + test ("replaceAll replaces all occurrences", fun _ -> + assertThat (replaceAll "aabbaa" "aa" "XX") (isEqualTo "XXbbXX")) + + test ("toInteger parses valid integer", fun _ -> + match toInteger "42abc" with + | Ok (n, rest) -> + assertThat n (isEqualTo 42) + assertThat rest (isEqualTo "abc") + | Error _ -> assertThat false (isTrue)) + + test ("toInteger returns error for non-integer", fun _ -> + match toInteger "abc" with + | Error _ -> assertThat true (isTrue) + | Ok _ -> assertThat false (isTrue)) + + test ("toFloat parses valid float", fun _ -> + match toFloat "3.14rest" with + | Ok (f, _) -> assertThat ((f > 3.13 && f < 3.15)) (isTrue) + | Error _ -> assertThat false (isTrue)) + + test ("toGraphemes splits into grapheme clusters", fun _ -> + let graphemes = toGraphemes "abc" + assertThat (Array.length graphemes) (isEqualTo 3) + assertThat (graphemes.[0]) (isEqualTo "a") + assertThat (graphemes.[1]) (isEqualTo "b") + assertThat (graphemes.[2]) (isEqualTo "c") + ) + + test ("splitAllRaw returns the native list form of splitAll", fun _ -> + let parts: BeamList = splitAllRaw "a,b,c" "," + let expected: BeamList = emitErlExpr () "[<<\"a\">>, <<\"b\">>, <<\"c\">>]" + assertThat parts (isEqualTo expected)) + + test ("splitFirstRaw returns the native list form of splitFirst", fun _ -> + let parts: BeamList = splitFirstRaw "hello world" " " + let expected: BeamList = emitErlExpr () "[<<\"hello\">>, <<\"world\">>]" + assertThat parts (isEqualTo expected)) ] + ) diff --git a/test/TestSupervisor.fs b/test/TestSupervisor.fs index f66e994..4fa9a90 100644 --- a/test/TestSupervisor.fs +++ b/test/TestSupervisor.fs @@ -1,23 +1,11 @@ module Fable.Beam.Tests.Supervisor -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Beam open Fable.Beam.Supervisor -#endif - -[] -let ``test supervisor.which_children on non-existent catches error`` () = -#if FABLE_COMPILER - try - supervisor.which_children (fromName (Fable.Beam.Erlang.binaryToAtom "nonexistent_sup_xyz")) - |> ignore - with _ -> - () -#else - () -#endif #if FABLE_COMPILER // Starts a fresh test_basic_sup supervisor (one temporary `counter` child) and @@ -28,55 +16,50 @@ let private startSup () : SupRef = | Error _ -> failwith "test_basic_sup should start" #endif -[] -let ``test supervisor.terminate_child succeeds for a running child`` () = -#if FABLE_COMPILER - let sup = startSup () +let tests = + testList ( + "Supervisor", + [ test ("supervisor.which_children on non-existent catches error", fun _ -> + try + supervisor.which_children (fromName (Erlang.binaryToAtom "nonexistent_sup_xyz")) + |> ignore + with _ -> () + ) - // Bare `ok` from OTP must surface as Ok () on the F# side. - match supervisor.terminate_child (sup, Erlang.binaryToAtom "counter") with - | Ok() -> () - | Error _ -> failwith "terminate_child should succeed for a known child" -#else - () -#endif + test ("supervisor.terminate_child succeeds for a running child", fun _ -> + let sup = startSup () -[] -let ``test supervisor.terminate_child returns Error not_found for unknown id`` () = -#if FABLE_COMPILER - let sup = startSup () + // Bare `ok` from OTP must surface as Ok () on the F# side. + match supervisor.terminate_child (sup, Erlang.binaryToAtom "counter") with + | Ok () -> assertThat true (isTrue) + | Error _ -> failwith "terminate_child should succeed for a known child" + ) - match supervisor.terminate_child (sup, Erlang.binaryToAtom "nope") with - | Ok() -> failwith "terminate_child should fail for an unknown child" - | Error reason -> reason |> equal (Erlang.binaryToAtom "not_found") -#else - () -#endif + test ("supervisor.terminate_child returns Error not_found for unknown id", fun _ -> + let sup = startSup () -[] -let ``test supervisor.delete_child succeeds after terminate`` () = -#if FABLE_COMPILER - let sup = startSup () - let counter = Erlang.binaryToAtom "counter" + match supervisor.terminate_child (sup, Erlang.binaryToAtom "nope") with + | Ok () -> failwith "terminate_child should fail for an unknown child" + | Error reason -> assertThat reason (isEqualTo (Erlang.binaryToAtom "not_found")) + ) - // A child spec can only be deleted once the child is terminated. - supervisor.terminate_child (sup, counter) |> ignore + test ("supervisor.delete_child succeeds after terminate", fun _ -> + let sup = startSup () + let counter = Erlang.binaryToAtom "counter" - match supervisor.delete_child (sup, counter) with - | Ok() -> () - | Error _ -> failwith "delete_child should succeed for a terminated child" -#else - () -#endif + // A child spec can only be deleted once the child is terminated. + supervisor.terminate_child (sup, counter) |> ignore -[] -let ``test supervisor.delete_child returns Error not_found for unknown id`` () = -#if FABLE_COMPILER - let sup = startSup () + match supervisor.delete_child (sup, counter) with + | Ok () -> assertThat true (isTrue) + | Error _ -> failwith "delete_child should succeed for a terminated child" + ) - match supervisor.delete_child (sup, Erlang.binaryToAtom "nope") with - | Ok() -> failwith "delete_child should fail for an unknown child" - | Error reason -> reason |> equal (Erlang.binaryToAtom "not_found") -#else - () -#endif + test ("supervisor.delete_child returns Error not_found for unknown id", fun _ -> + let sup = startSup () + + match supervisor.delete_child (sup, Erlang.binaryToAtom "nope") with + | Ok () -> failwith "delete_child should fail for an unknown child" + | Error reason -> assertThat reason (isEqualTo (Erlang.binaryToAtom "not_found")) + ) ] + ) diff --git a/test/TestTimer.fs b/test/TestTimer.fs index 8cd3850..3356cb5 100644 --- a/test/TestTimer.fs +++ b/test/TestTimer.fs @@ -1,39 +1,19 @@ module Fable.Beam.Tests.Timer -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Beam.Timer -#endif -[] -let ``test timer.hours converts correctly`` () = -#if FABLE_COMPILER - timer.hours 1 |> equal 3600000 -#else - () -#endif +let tests = + testList ( + "Timer", + [ test ("hours converts correctly", fun _ -> assertThat (timer.hours 1) (isEqualTo 3600000)) -[] -let ``test timer.minutes converts correctly`` () = -#if FABLE_COMPILER - timer.minutes 1 |> equal 60000 -#else - () -#endif + test ("minutes converts correctly", fun _ -> assertThat (timer.minutes 1) (isEqualTo 60000)) -[] -let ``test timer.seconds converts correctly`` () = -#if FABLE_COMPILER - timer.seconds 1 |> equal 1000 -#else - () -#endif + test ("seconds converts correctly", fun _ -> assertThat (timer.seconds 1) (isEqualTo 1000)) -[] -let ``test timer.sleep works`` () = -#if FABLE_COMPILER - timer.sleep 10 -#else - () -#endif + test ("sleep does not crash", fun _ -> timer.sleep 10) ] + ) diff --git a/test/TestUriString.fs b/test/TestUriString.fs index 2d386af..807d8c8 100644 --- a/test/TestUriString.fs +++ b/test/TestUriString.fs @@ -1,250 +1,116 @@ module Fable.Beam.Tests.UriString -open Fable.Beam.Testing +open Scriptorium.Quill +open Scriptorium.Nib.Assertion +open type Scriptorium.Quill.Test -#if FABLE_COMPILER open Fable.Core open Fable.Core.BeamInterop open Fable.Beam.UriString -#endif - -// ============================================================================ -// parse + accessors -// ============================================================================ - -[] -let ``test parse full uri`` () = -#if FABLE_COMPILER - match parse "https://user:pass@example.com:8080/path?q=hello#frag" with - | Ok uri -> - scheme uri |> equal (Some "https") - userinfo uri |> equal (Some "user:pass") - host uri |> equal (Some "example.com") - port uri |> equal (Some 8080) - path uri |> equal (Some "/path") - query uri |> equal (Some "q=hello") - fragment uri |> equal (Some "frag") - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test parse minimal uri`` () = -#if FABLE_COMPILER - match parse "https://example.com" with - | Ok uri -> - scheme uri |> equal (Some "https") - host uri |> equal (Some "example.com") - port uri |> equal None - query uri |> equal None - fragment uri |> equal None - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test parse relative uri has no scheme or host`` () = -#if FABLE_COMPILER - match parse "/relative/path" with - | Ok uri -> - scheme uri |> equal None - host uri |> equal None - path uri |> equal (Some "/relative/path") - | Error _ -> false |> equal true -#else - () -#endif - -[] -let ``test parse path only uri`` () = -#if FABLE_COMPILER - match parse "just/a/path" with - | Ok uri -> - scheme uri |> equal None - host uri |> equal None - path uri |> equal (Some "just/a/path") - | Error _ -> false |> equal true -#else - () -#endif - -// ============================================================================ -// normalize -// ============================================================================ - -[] -let ``test normalize lowercases scheme and host`` () = -#if FABLE_COMPILER - normalize "HTTP://EXAMPLE.COM/path" |> equal (Ok "http://example.com/path") -#else - () -#endif - -[] -let ``test normalize removes default http port`` () = -#if FABLE_COMPILER - normalize "http://example.com:80/path" |> equal (Ok "http://example.com/path") -#else - () -#endif - -[] -let ``test normalize removes default https port`` () = -#if FABLE_COMPILER - normalize "https://example.com:443/path" - |> equal (Ok "https://example.com/path") -#else - () -#endif - -[] -let ``test normalize resolves dot segments`` () = -#if FABLE_COMPILER - normalize "http://example.com/a/b/../c" |> equal (Ok "http://example.com/a/c") -#else - () -#endif - -// ============================================================================ -// resolve -// ============================================================================ - -[] -let ``test resolve absolute path reference`` () = -#if FABLE_COMPILER - resolve "/new" "https://example.com/old/page" - |> equal (Ok "https://example.com/new") -#else - () -#endif - -[] -let ``test resolve relative path reference`` () = -#if FABLE_COMPILER - resolve "new" "https://example.com/old/page" - |> equal (Ok "https://example.com/old/new") -#else - () -#endif - -[] -let ``test resolve full uri preserves reference`` () = -#if FABLE_COMPILER - resolve "https://other.com/path" "https://example.com/base" - |> equal (Ok "https://other.com/path") -#else - () -#endif - -// ============================================================================ -// dissectQuery / composeQuery -// ============================================================================ - -[] -let ``test dissect query parses key value pairs`` () = -#if FABLE_COMPILER - dissectQuery "q=hello&lang=en" |> equal [ ("q", "hello"); ("lang", "en") ] -#else - () -#endif - -[] -let ``test dissect query empty string`` () = -#if FABLE_COMPILER - dissectQuery "" |> equal [] -#else - () -#endif - -[] -let ``test compose query builds query string`` () = -#if FABLE_COMPILER - composeQuery [ ("q", "search"); ("page", "1") ] |> equal "q=search&page=1" -#else - () -#endif - -[] -let ``test compose query empty list`` () = -#if FABLE_COMPILER - composeQuery [] |> equal "" -#else - () -#endif - -[] -let ``test dissect and compose query roundtrip`` () = -#if FABLE_COMPILER - let original = "name=Alice&role=admin" - original |> dissectQuery |> composeQuery |> equal original -#else - () -#endif - -// ============================================================================ -// percentDecode -// ============================================================================ - -[] -let ``test percent decode decodes encoded chars`` () = -#if FABLE_COMPILER - percentDecode "hello%20world" |> equal (Ok "hello world") -#else - () -#endif - -[] -let ``test percent decode passthrough for plain string`` () = -#if FABLE_COMPILER - percentDecode "hello" |> equal (Ok "hello") -#else - () -#endif - -[] -let ``test percent decode returns error for malformed encoding`` () = -#if FABLE_COMPILER - match percentDecode "invalid%GG" with - | Error _ -> true |> equal true - | Ok _ -> false |> equal true -#else - () -#endif - -// ============================================================================ -// quote / quoteWith / unquote -// ============================================================================ - -[] -let ``test quote encodes spaces and slashes`` () = -#if FABLE_COMPILER - quote "hello world" |> equal "hello%20world" -#else - () -#endif - -[] -let ``test quote with safe chars preserves slash`` () = -#if FABLE_COMPILER - quoteWith "hello/world" "/" |> equal "hello/world" -#else - () -#endif - -[] -let ``test unquote decodes percent encoded string`` () = -#if FABLE_COMPILER - unquote "hello%20world" |> equal "hello world" -#else - () -#endif - -[] -let ``test unquote passthrough for plain string`` () = -#if FABLE_COMPILER - unquote "hello" |> equal "hello" -#else - () -#endif + +let tests = + testList ( + "UriString", + [ test ("parse full uri", fun _ -> + match parse "https://user:pass@example.com:8080/path?q=hello#frag" with + | Ok uri -> + assertThat (scheme uri) (isEqualTo (Some "https")) + assertThat (userinfo uri) (isEqualTo (Some "user:pass")) + assertThat (host uri) (isEqualTo (Some "example.com")) + assertThat (port uri) (isEqualTo (Some 8080)) + assertThat (path uri) (isEqualTo (Some "/path")) + assertThat (query uri) (isEqualTo (Some "q=hello")) + assertThat (fragment uri) (isEqualTo (Some "frag")) + | Error _ -> failwith "expected a parsed uri" + ) + + test ("parse minimal uri", fun _ -> + match parse "https://example.com" with + | Ok uri -> + assertThat (scheme uri) (isEqualTo (Some "https")) + assertThat (host uri) (isEqualTo (Some "example.com")) + assertThat (port uri) (isEqualTo None) + assertThat (query uri) (isEqualTo None) + assertThat (fragment uri) (isEqualTo None) + | Error _ -> failwith "expected a parsed uri" + ) + + test ("parse relative uri has no scheme or host", fun _ -> + match parse "/relative/path" with + | Ok uri -> + assertThat (scheme uri) (isEqualTo None) + assertThat (host uri) (isEqualTo None) + assertThat (path uri) (isEqualTo (Some "/relative/path")) + | Error _ -> failwith "expected a parsed uri" + ) + + test ("parse path only uri", fun _ -> + match parse "just/a/path" with + | Ok uri -> + assertThat (scheme uri) (isEqualTo None) + assertThat (host uri) (isEqualTo None) + assertThat (path uri) (isEqualTo (Some "just/a/path")) + | Error _ -> failwith "expected a parsed uri" + ) + + test ("normalize lowercases scheme and host", fun _ -> + assertThat (normalize "HTTP://EXAMPLE.COM/path") (isEqualTo (Ok "http://example.com/path"))) + + test ("normalize removes default http port", fun _ -> + assertThat (normalize "http://example.com:80/path") (isEqualTo (Ok "http://example.com/path"))) + + test ("normalize removes default https port", fun _ -> + assertThat (normalize "https://example.com:443/path") (isEqualTo (Ok "https://example.com/path"))) + + test ("normalize resolves dot segments", fun _ -> + assertThat (normalize "http://example.com/a/b/../c") (isEqualTo (Ok "http://example.com/a/c"))) + + test ("resolve absolute path reference", fun _ -> + assertThat (resolve "/new" "https://example.com/old/page") (isEqualTo (Ok "https://example.com/new"))) + + test ("resolve relative path reference", fun _ -> + assertThat (resolve "new" "https://example.com/old/page") (isEqualTo (Ok "https://example.com/old/new"))) + + test ("resolve full uri preserves reference", fun _ -> + assertThat (resolve "https://other.com/path" "https://example.com/base") (isEqualTo (Ok "https://other.com/path"))) + + test ("dissect query parses key value pairs", fun _ -> + assertThat (dissectQuery "q=hello&lang=en") (isEqualTo [ ("q", "hello"); ("lang", "en") ])) + + test ("dissect query empty string", fun _ -> + assertThat (dissectQuery "") (isEqualTo [])) + + test ("compose query builds query string", fun _ -> + assertThat (composeQuery [ ("q", "search"); ("page", "1") ]) (isEqualTo "q=search&page=1")) + + test ("compose query empty list", fun _ -> + assertThat (composeQuery []) (isEqualTo "")) + + test ("dissect and compose query roundtrip", fun _ -> + let original = "name=Alice&role=admin" + assertThat (original |> dissectQuery |> composeQuery) (isEqualTo original)) + + test ("percent decode decodes encoded chars", fun _ -> + assertThat (percentDecode "hello%20world") (isEqualTo (Ok "hello world"))) + + test ("percent decode passthrough for plain string", fun _ -> + assertThat (percentDecode "hello") (isEqualTo (Ok "hello"))) + + test ("percent decode returns error for malformed encoding", fun _ -> + match percentDecode "invalid%GG" with + | Error _ -> assertThat true (isTrue) + | Ok _ -> failwith "expected a malformed-encoding error" + ) + + test ("quote encodes spaces and slashes", fun _ -> + assertThat (quote "hello world") (isEqualTo "hello%20world")) + + test ("quote with safe chars preserves slash", fun _ -> + assertThat (quoteWith "hello/world" "/") (isEqualTo "hello/world")) + + test ("unquote decodes percent encoded string", fun _ -> + assertThat (unquote "hello%20world") (isEqualTo "hello world")) + + test ("unquote passthrough for plain string", fun _ -> + assertThat (unquote "hello") (isEqualTo "hello")) ] + ) diff --git a/test/paket.references b/test/paket.references deleted file mode 100644 index 720902f..0000000 --- a/test/paket.references +++ /dev/null @@ -1,9 +0,0 @@ -group Test - -FSharp.Core -Fable.Core - -Microsoft.NET.Test.Sdk - -xunit -xunit.runner.visualstudio