diff --git a/.config/nextest.toml b/.config/nextest.toml index 4beb89f..f0f9abf 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,4 +1,7 @@ # Default profile — used by `just test` and ad-hoc `cargo nextest run`. +# `just test` applies `-E 'not binary(real_world_roundtrip)'` to filter +# out network-dependent tests. Run them explicitly via +# `just test-real-world` / `just test-all`. [profile.default] # Surface failing test output inline as it happens, then repeat the # summary at the end. On success, stay quiet — use `just test-verbose` @@ -11,10 +14,8 @@ success-output = "never" fail-fast = false # Network-gated real-world roundtrip overrides. Absorb flaky DNS/HTTP -# with a couple of retries, warn when a single attempt takes more than -# 30s (a dead resolver, usually), and hard-kill after 3 minutes to -# prevent wedged CI. The binary filter covers every `roundtrip_*` test -# added via the `roundtrip_test!` macro. +# with retries, warn on slow single attempts, hard-kill wedged runs. +# Applies whenever the roundtrip binary runs, under any profile. # # No test-group here: nextest runs each test in its own OS process, so # `std::env::set_current_dir` calls are isolated per test. Each of the diff --git a/Cargo.lock b/Cargo.lock index 2f277f3..5a1ef16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,6 +382,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys 0.61.1", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -505,6 +516,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1107,6 +1124,19 @@ version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +[[package]] +name = "insta" +version = "1.47.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" +dependencies = [ + "console", + "once_cell", + "serde", + "similar", + "tempfile", +] + [[package]] name = "inventory" version = "0.3.21" @@ -1443,6 +1473,7 @@ name = "mlf-integration-tests" version = "0.1.0" dependencies = [ "datatest-stable", + "insta", "mlf-cli", "mlf-codegen", "mlf-diagnostics", @@ -2191,6 +2222,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "slab" version = "0.4.11" diff --git a/justfile b/justfile index 3feafcb..dd0aa94 100644 --- a/justfile +++ b/justfile @@ -6,18 +6,25 @@ # Default: run all tests default: test -# Install developer tools (test runner, etc.) +# Install developer tools (test runner, snapshot reviewer) install-tools: cargo install cargo-nextest --locked + cargo install cargo-insta --locked # Run every test across the workspace (excluding packages we can't build -# locally). Nextest already parallelises across binaries, so a single -# invocation is both faster and more comprehensive than chaining the -# per-suite recipes below — those remain useful for targeted runs. +# locally). Nextest parallelises across binaries, so a single invocation +# is both faster and more comprehensive than chaining the per-suite +# recipes below — those remain useful for targeted runs. Use +# `just test-fast` to skip network-dependent real-world tests. test: @echo "Running all workspace tests..." cargo nextest run --workspace --exclude tree-sitter-mlf --exclude mlf-wasm +# Run everything except network-dependent real-world roundtrip tests +test-fast: + @echo "Running workspace tests (excluding real-world)..." + cargo nextest run --workspace --exclude tree-sitter-mlf --exclude mlf-wasm -E 'not binary(real_world_roundtrip)' + # Run only language tests (mlf-lang crate) test-lang: @echo "Running mlf-lang integration tests..." @@ -54,10 +61,14 @@ test-workspace: cargo nextest run -p mlf-integration-tests --test workspace_integration # Run only the network-dependent real-world round-trip tests -test-real-world: +test-real: @echo "\n🌐 Running real-world round-trip tests (fetches from network)..." cargo nextest run -p mlf-integration-tests --test real_world_roundtrip +# Review pending insta snapshot changes (accepts / rejects `.snap.new` files) +review: + cargo insta review + # Run tests with verbose output (stdout streamed live, tests run serially) test-verbose: cargo nextest run --workspace --exclude tree-sitter-mlf --exclude mlf-wasm --no-capture diff --git a/tests/Cargo.toml b/tests/Cargo.toml index f12cb50..34d6d3b 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -20,6 +20,7 @@ toml = "0.8" tokio = { version = "1", features = ["full"] } tempfile = "3.8" datatest-stable = "0.3" +insta = { version = "1.43", features = ["json"] } [dev-dependencies] # Any additional test dependencies diff --git a/tests/codegen/lexicon/annotations/expected.json b/tests/codegen/lexicon/annotations/lexicon@annotations.snap similarity index 73% rename from tests/codegen/lexicon/annotations/expected.json rename to tests/codegen/lexicon/annotations/lexicon@annotations.snap index bc59188..28b674b 100644 --- a/tests/codegen/lexicon/annotations/expected.json +++ b/tests/codegen/lexicon/annotations/lexicon@annotations.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,7 +12,9 @@ "key": "custom-key", "record": { "type": "object", - "required": ["text"], + "required": [ + "text" + ], "properties": { "text": { "type": "string" diff --git a/tests/codegen/lexicon/annotations/warnings@annotations.snap b/tests/codegen/lexicon/annotations/warnings@annotations.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/annotations/warnings@annotations.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/basic_record/lexicon@basic_record.snap b/tests/codegen/lexicon/basic_record/lexicon@basic_record.snap new file mode 100644 index 0000000..fc462b3 --- /dev/null +++ b/tests/codegen/lexicon/basic_record/lexicon@basic_record.snap @@ -0,0 +1,30 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- +{ + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "id": "app.bsky.feed.post", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": [ + "text", + "createdAt" + ], + "properties": { + "text": { + "type": "string" + }, + "createdAt": { + "type": "string" + } + } + } + } + } +} diff --git a/tests/codegen/lexicon/basic_record/warnings@basic_record.snap b/tests/codegen/lexicon/basic_record/warnings@basic_record.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/basic_record/warnings@basic_record.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/const_float_stringifies/expected.json b/tests/codegen/lexicon/const_float_stringifies/expected.json deleted file mode 100644 index df16f5c..0000000 --- a/tests/codegen/lexicon/const_float_stringifies/expected.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$type": "com.atproto.lexicon.schema", - "lexicon": 1, - "id": "com.example.const_float_stringifies", - "x-threshold": "3.14", - "x-revision": 3, - "defs": { - "main": { - "type": "record", - "key": "tid", - "record": { - "type": "object", - "required": ["name"], - "properties": { - "name": {"type": "string"} - } - } - } - } -} diff --git a/tests/codegen/lexicon/const_float_stringifies/expected_warnings.txt b/tests/codegen/lexicon/const_float_stringifies/expected_warnings.txt deleted file mode 100644 index 08981b0..0000000 --- a/tests/codegen/lexicon/const_float_stringifies/expected_warnings.txt +++ /dev/null @@ -1 +0,0 @@ -com.example.const_float_stringifies: @const("x-threshold", 3.14): ATProto's data model has no floats; emitting as string "3.14" to stay spec-compliant diff --git a/tests/codegen/lexicon/basic_record/expected.json b/tests/codegen/lexicon/const_float_stringifies/lexicon@const_float_stringifies.snap similarity index 53% rename from tests/codegen/lexicon/basic_record/expected.json rename to tests/codegen/lexicon/const_float_stringifies/lexicon@const_float_stringifies.snap index 4daf9b2..0ba2d7c 100644 --- a/tests/codegen/lexicon/basic_record/expected.json +++ b/tests/codegen/lexicon/const_float_stringifies/lexicon@const_float_stringifies.snap @@ -1,19 +1,24 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, - "id": "app.bsky.feed.post", + "id": "com.example.const_float_stringifies", + "x-threshold": "3.14", + "x-revision": 3, "defs": { "main": { "type": "record", "key": "tid", "record": { "type": "object", - "required": ["text", "createdAt"], + "required": [ + "name" + ], "properties": { - "text": { - "type": "string" - }, - "createdAt": { + "name": { "type": "string" } } diff --git a/tests/codegen/lexicon/const_float_stringifies/test.toml b/tests/codegen/lexicon/const_float_stringifies/test.toml deleted file mode 100644 index f63012b..0000000 --- a/tests/codegen/lexicon/const_float_stringifies/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "const_float_stringifies" -description = "@const with a fractional numeric value stringifies to stay spec-compliant and emits a warning" -namespace = "com.example.const_float_stringifies" diff --git a/tests/codegen/lexicon/const_float_stringifies/warnings@const_float_stringifies.snap b/tests/codegen/lexicon/const_float_stringifies/warnings@const_float_stringifies.snap new file mode 100644 index 0000000..ea2b4dc --- /dev/null +++ b/tests/codegen/lexicon/const_float_stringifies/warnings@const_float_stringifies.snap @@ -0,0 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[ + "com.example.const_float_stringifies: @const(\"x-threshold\", 3.14): ATProto's data model has no floats; emitting as string \"3.14\" to stay spec-compliant", +] diff --git a/tests/codegen/lexicon/doc_comments/expected.json b/tests/codegen/lexicon/doc_comments/lexicon@doc_comments.snap similarity index 89% rename from tests/codegen/lexicon/doc_comments/expected.json rename to tests/codegen/lexicon/doc_comments/lexicon@doc_comments.snap index 676c8c4..1e962a7 100644 --- a/tests/codegen/lexicon/doc_comments/expected.json +++ b/tests/codegen/lexicon/doc_comments/lexicon@doc_comments.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, diff --git a/tests/codegen/lexicon/doc_comments/warnings@doc_comments.snap b/tests/codegen/lexicon/doc_comments/warnings@doc_comments.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/doc_comments/warnings@doc_comments.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/implicit_main/expected.json b/tests/codegen/lexicon/implicit_main/expected.json deleted file mode 100644 index 8389864..0000000 --- a/tests/codegen/lexicon/implicit_main/expected.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$type": "com.atproto.lexicon.schema", - "lexicon": 1, - "id": "com.example.profile", - "defs": { - "main": { - "type": "record", - "key": "tid", - "record": { - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string" - }, - "color": { - "type": "ref", - "ref": "#color" - } - } - } - }, - "color": { - "type": "object", - "required": ["red", "green", "blue"], - "properties": { - "red": { - "type": "integer" - }, - "green": { - "type": "integer" - }, - "blue": { - "type": "integer" - } - } - } - } -} diff --git a/tests/codegen/lexicon/implicit_main/lexicon@implicit_main.snap b/tests/codegen/lexicon/implicit_main/lexicon@implicit_main.snap new file mode 100644 index 0000000..2214637 --- /dev/null +++ b/tests/codegen/lexicon/implicit_main/lexicon@implicit_main.snap @@ -0,0 +1,49 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- +{ + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "id": "com.example.profile", + "defs": { + "color": { + "type": "object", + "required": [ + "red", + "green", + "blue" + ], + "properties": { + "red": { + "type": "integer" + }, + "green": { + "type": "integer" + }, + "blue": { + "type": "integer" + } + } + }, + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "color": { + "type": "ref", + "ref": "#color" + } + } + } + } + } +} diff --git a/tests/codegen/lexicon/implicit_main/warnings@implicit_main.snap b/tests/codegen/lexicon/implicit_main/warnings@implicit_main.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/implicit_main/warnings@implicit_main.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/integer_constraints/expected.json b/tests/codegen/lexicon/integer_constraints/lexicon@integer_constraints.snap similarity index 80% rename from tests/codegen/lexicon/integer_constraints/expected.json rename to tests/codegen/lexicon/integer_constraints/lexicon@integer_constraints.snap index eedaccb..56c97bf 100644 --- a/tests/codegen/lexicon/integer_constraints/expected.json +++ b/tests/codegen/lexicon/integer_constraints/lexicon@integer_constraints.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,7 +12,10 @@ "key": "tid", "record": { "type": "object", - "required": ["age", "temperature"], + "required": [ + "age", + "temperature" + ], "properties": { "age": { "type": "integer", diff --git a/tests/codegen/lexicon/integer_constraints/test.toml b/tests/codegen/lexicon/integer_constraints/test.toml deleted file mode 100644 index 1143de6..0000000 --- a/tests/codegen/lexicon/integer_constraints/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "integer_constraints" -description = "Test integer_constraints" -namespace = "com.example.integer_constraints" diff --git a/tests/codegen/lexicon/integer_constraints/warnings@integer_constraints.snap b/tests/codegen/lexicon/integer_constraints/warnings@integer_constraints.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/integer_constraints/warnings@integer_constraints.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/item_extensions_codegen/expected.json b/tests/codegen/lexicon/item_extensions_codegen/lexicon@item_extensions_codegen.snap similarity index 64% rename from tests/codegen/lexicon/item_extensions_codegen/expected.json rename to tests/codegen/lexicon/item_extensions_codegen/lexicon@item_extensions_codegen.snap index 8eafe5c..7f91043 100644 --- a/tests/codegen/lexicon/item_extensions_codegen/expected.json +++ b/tests/codegen/lexicon/item_extensions_codegen/lexicon@item_extensions_codegen.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,9 +12,13 @@ "key": "tid", "record": { "type": "object", - "required": ["name"], + "required": [ + "name" + ], "properties": { - "name": {"type": "string"} + "name": { + "type": "string" + } } }, "x-deprecated": true, diff --git a/tests/codegen/lexicon/item_extensions_codegen/test.toml b/tests/codegen/lexicon/item_extensions_codegen/test.toml deleted file mode 100644 index 056fab4..0000000 --- a/tests/codegen/lexicon/item_extensions_codegen/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "item_extensions_codegen" -description = "@const annotations on a record emit as extra JSON fields on the record def" -namespace = "com.example.item_extensions_codegen" diff --git a/tests/codegen/lexicon/item_extensions_codegen/warnings@item_extensions_codegen.snap b/tests/codegen/lexicon/item_extensions_codegen/warnings@item_extensions_codegen.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/item_extensions_codegen/warnings@item_extensions_codegen.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/local_references/expected.json b/tests/codegen/lexicon/local_references/lexicon@local_references.snap similarity index 50% rename from tests/codegen/lexicon/local_references/expected.json rename to tests/codegen/lexicon/local_references/lexicon@local_references.snap index 1bbdbc7..22e30ab 100644 --- a/tests/codegen/lexicon/local_references/expected.json +++ b/tests/codegen/lexicon/local_references/lexicon@local_references.snap @@ -1,14 +1,36 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, "id": "com.example.document", "defs": { + "metadata": { + "type": "object", + "required": [ + "createdAt", + "updatedAt" + ], + "properties": { + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "main": { "type": "record", "key": "tid", "record": { "type": "object", - "required": ["title", "meta"], + "required": [ + "title", + "meta" + ], "properties": { "title": { "type": "string" @@ -19,18 +41,6 @@ } } } - }, - "metadata": { - "type": "object", - "required": ["createdAt", "updatedAt"], - "properties": { - "createdAt": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } } } } diff --git a/tests/codegen/lexicon/local_references/warnings@local_references.snap b/tests/codegen/lexicon/local_references/warnings@local_references.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/local_references/warnings@local_references.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/nested_objects/expected.json b/tests/codegen/lexicon/nested_objects/lexicon@nested_objects.snap similarity index 75% rename from tests/codegen/lexicon/nested_objects/expected.json rename to tests/codegen/lexicon/nested_objects/lexicon@nested_objects.snap index 33aeafb..d97678f 100644 --- a/tests/codegen/lexicon/nested_objects/expected.json +++ b/tests/codegen/lexicon/nested_objects/lexicon@nested_objects.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,28 +12,36 @@ "key": "tid", "record": { "type": "object", - "required": ["displayName"], + "required": [ + "displayName" + ], "properties": { "displayName": { "type": "string" }, "metadata": { "type": "object", - "required": ["createdAt"], + "required": [ + "createdAt" + ], "properties": { "createdAt": { "type": "string" }, "settings": { "type": "object", - "required": ["theme"], + "required": [ + "theme" + ], "properties": { "theme": { "type": "string" }, "notifications": { "type": "object", - "required": ["email"], + "required": [ + "email" + ], "properties": { "email": { "type": "boolean" diff --git a/tests/codegen/lexicon/nested_objects/test.toml b/tests/codegen/lexicon/nested_objects/test.toml deleted file mode 100644 index a864da0..0000000 --- a/tests/codegen/lexicon/nested_objects/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "nested_objects" -description = "Test nested_objects" -namespace = "com.example.nested_objects" diff --git a/tests/codegen/lexicon/nested_objects/warnings@nested_objects.snap b/tests/codegen/lexicon/nested_objects/warnings@nested_objects.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/nested_objects/warnings@nested_objects.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/record_key_any/expected.json b/tests/codegen/lexicon/record_key_any/lexicon@record_key_any.snap similarity index 74% rename from tests/codegen/lexicon/record_key_any/expected.json rename to tests/codegen/lexicon/record_key_any/lexicon@record_key_any.snap index d2b37e5..95064fd 100644 --- a/tests/codegen/lexicon/record_key_any/expected.json +++ b/tests/codegen/lexicon/record_key_any/lexicon@record_key_any.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,7 +12,11 @@ "key": "any", "record": { "type": "object", - "required": ["title", "content", "slug"], + "required": [ + "title", + "content", + "slug" + ], "properties": { "title": { "type": "string" diff --git a/tests/codegen/lexicon/record_key_any/test.toml b/tests/codegen/lexicon/record_key_any/test.toml deleted file mode 100644 index 82771d5..0000000 --- a/tests/codegen/lexicon/record_key_any/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "record_key_any" -description = "Test record_key_any" -namespace = "com.example.record_key_any" diff --git a/tests/codegen/lexicon/record_key_any/warnings@record_key_any.snap b/tests/codegen/lexicon/record_key_any/warnings@record_key_any.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/record_key_any/warnings@record_key_any.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/record_key_literal/expected.json b/tests/codegen/lexicon/record_key_literal/lexicon@record_key_literal.snap similarity index 78% rename from tests/codegen/lexicon/record_key_literal/expected.json rename to tests/codegen/lexicon/record_key_literal/lexicon@record_key_literal.snap index 716770e..454d968 100644 --- a/tests/codegen/lexicon/record_key_literal/expected.json +++ b/tests/codegen/lexicon/record_key_literal/lexicon@record_key_literal.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,7 +12,9 @@ "key": "literal:self", "record": { "type": "object", - "required": ["displayName"], + "required": [ + "displayName" + ], "properties": { "displayName": { "type": "string" diff --git a/tests/codegen/lexicon/record_key_literal/test.toml b/tests/codegen/lexicon/record_key_literal/test.toml deleted file mode 100644 index 83999c0..0000000 --- a/tests/codegen/lexicon/record_key_literal/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "record_key_literal" -description = "Test record_key_literal" -namespace = "com.example.record_key_literal" diff --git a/tests/codegen/lexicon/record_key_literal/warnings@record_key_literal.snap b/tests/codegen/lexicon/record_key_literal/warnings@record_key_literal.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/record_key_literal/warnings@record_key_literal.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/self_item/expected.json b/tests/codegen/lexicon/self_item/lexicon@self_item.snap similarity index 64% rename from tests/codegen/lexicon/self_item/expected.json rename to tests/codegen/lexicon/self_item/lexicon@self_item.snap index 6850fb0..d726c83 100644 --- a/tests/codegen/lexicon/self_item/expected.json +++ b/tests/codegen/lexicon/self_item/lexicon@self_item.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -9,9 +13,13 @@ "defs": { "main": { "type": "object", - "required": ["value"], + "required": [ + "value" + ], "properties": { - "value": {"type": "string"} + "value": { + "type": "string" + } } } } diff --git a/tests/codegen/lexicon/self_item/test.toml b/tests/codegen/lexicon/self_item/test.toml deleted file mode 100644 index 25f768b..0000000 --- a/tests/codegen/lexicon/self_item/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "self_item" -description = "self {} with docs and @const/@reference extensions emit as top-level JSON fields" -namespace = "com.example.self_item" diff --git a/tests/codegen/lexicon/self_item/warnings@self_item.snap b/tests/codegen/lexicon/self_item/warnings@self_item.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/self_item/warnings@self_item.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/string_constraints/expected.json b/tests/codegen/lexicon/string_constraints/lexicon@string_constraints.snap similarity index 81% rename from tests/codegen/lexicon/string_constraints/expected.json rename to tests/codegen/lexicon/string_constraints/lexicon@string_constraints.snap index a763df2..b04747f 100644 --- a/tests/codegen/lexicon/string_constraints/expected.json +++ b/tests/codegen/lexicon/string_constraints/lexicon@string_constraints.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,7 +12,10 @@ "key": "tid", "record": { "type": "object", - "required": ["username", "displayName"], + "required": [ + "username", + "displayName" + ], "properties": { "username": { "type": "string", diff --git a/tests/codegen/lexicon/string_constraints/test.toml b/tests/codegen/lexicon/string_constraints/test.toml deleted file mode 100644 index 05404cc..0000000 --- a/tests/codegen/lexicon/string_constraints/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "string_constraints" -description = "Test string_constraints" -namespace = "com.example.string_constraints" diff --git a/tests/codegen/lexicon/string_constraints/warnings@string_constraints.snap b/tests/codegen/lexicon/string_constraints/warnings@string_constraints.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/string_constraints/warnings@string_constraints.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen/lexicon/union_types/expected.json b/tests/codegen/lexicon/union_types/lexicon@union_types.snap similarity index 51% rename from tests/codegen/lexicon/union_types/expected.json rename to tests/codegen/lexicon/union_types/lexicon@union_types.snap index f627bf7..0304dbf 100644 --- a/tests/codegen/lexicon/union_types/expected.json +++ b/tests/codegen/lexicon/union_types/lexicon@union_types.snap @@ -1,3 +1,7 @@ +--- +source: tests/codegen_integration.rs +expression: output.json +--- { "$type": "com.atproto.lexicon.schema", "lexicon": 1, @@ -8,21 +12,33 @@ "key": "tid", "record": { "type": "object", - "required": ["content"], + "required": [ + "content" + ], "properties": { "content": { "type": "union", "refs": [ - {"type": "string"}, - {"type": "integer"}, - {"type": "boolean"} + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "boolean" + } ] }, "media": { "type": "union", "refs": [ - {"type": "string"}, - {"type": "integer"} + { + "type": "string" + }, + { + "type": "integer" + } ], "closed": true } diff --git a/tests/codegen/lexicon/union_types/test.toml b/tests/codegen/lexicon/union_types/test.toml deleted file mode 100644 index f948118..0000000 --- a/tests/codegen/lexicon/union_types/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -[test] -name = "union_types" -description = "Test union_types" -namespace = "com.example.union_types" diff --git a/tests/codegen/lexicon/union_types/warnings@union_types.snap b/tests/codegen/lexicon/union_types/warnings@union_types.snap new file mode 100644 index 0000000..23a5161 --- /dev/null +++ b/tests/codegen/lexicon/union_types/warnings@union_types.snap @@ -0,0 +1,5 @@ +--- +source: tests/codegen_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/codegen_integration.rs b/tests/codegen_integration.rs index c332627..9a78f5d 100644 --- a/tests/codegen_integration.rs +++ b/tests/codegen_integration.rs @@ -1,31 +1,30 @@ // Workspace-level integration tests for code generation. // Each subdirectory of `codegen/lexicon/` is a test fixture containing -// `input.mlf`, `expected.json`, and optionally `test.toml`. +// `input.mlf`, optionally `test.toml`, and insta snapshot files +// (`lexicon.snap`, `warnings.snap`) checked into the fixture directory. +// +// To update snapshots after an intentional change: +// just test-codegen # produces .snap.new files +// just review # interactive accept/reject use mlf_codegen::generate_lexicon; use mlf_integration_tests::test_utils; use mlf_lang::{parser::parse_lexicon, Workspace}; -use serde_json::Value; use std::fs; use std::path::Path; -fn derive_namespace_fallback(test_name: &str) -> String { - match test_name { - "basic_record" => "app.bsky.feed.post".to_string(), - _ => format!("com.example.{}", test_name), - } -} - fn run_lexicon_test(input_path: &Path) -> datatest_stable::Result<()> { let test_dir = input_path.parent().ok_or("input.mlf has no parent directory")?; + let test_name = test_dir + .file_name() + .and_then(|s| s.to_str()) + .ok_or("input.mlf parent has no name")? + .to_string(); - let config = test_utils::load_test_config(test_dir, derive_namespace_fallback) - .map_err(|e| format!("{}", e))?; - - let namespace = config - .test - .namespace - .ok_or("No namespace specified in test.toml")?; + let namespace = test_utils::load_test_config(test_dir, |name| format!("com.example.{}", name)) + .ok() + .and_then(|c| c.test.namespace) + .unwrap_or_else(|| format!("com.example.{}", test_name)); let input = fs::read_to_string(input_path)?; let lexicon = parse_lexicon(&input).map_err(|e| format!("Failed to parse: {:?}", e))?; @@ -37,45 +36,22 @@ fn run_lexicon_test(input_path: &Path) -> datatest_stable::Result<()> { .map_err(|e| format!("Failed to resolve: {:?}", e))?; let lexicon = ws.get_lexicon(&namespace).ok_or("Module not found")?; - let output = generate_lexicon(&namespace, lexicon, &ws); - let expected_str = fs::read_to_string(test_dir.join("expected.json"))?; - let expected_json: Value = serde_json::from_str(&expected_str)?; - - if output.json != expected_json { - return Err(format!( - "Output mismatch:\nExpected:\n{}\n\nGot:\n{}", - serde_json::to_string_pretty(&expected_json).unwrap(), - serde_json::to_string_pretty(&output.json).unwrap() - ) - .into()); - } - - let warnings_path = test_dir.join("expected_warnings.txt"); - let expected_warnings = if warnings_path.exists() { - fs::read_to_string(&warnings_path)? - } else { - String::new() - }; - let actual_warnings = output + let formatted_warnings: Vec = output .warnings .iter() .map(|w| format!("{}: {}", w.namespace, w.message)) - .collect::>() - .join("\n"); - let actual_warnings = if actual_warnings.is_empty() { - String::new() - } else { - format!("{}\n", actual_warnings) - }; - if actual_warnings != expected_warnings { - return Err(format!( - "Warnings mismatch:\n--- expected ---\n{}\n--- got ---\n{}", - expected_warnings, actual_warnings - ) - .into()); - } + .collect(); + + insta::with_settings!({ + snapshot_path => test_dir.to_path_buf(), + prepend_module_to_snapshot => false, + snapshot_suffix => test_name, + }, { + insta::assert_json_snapshot!("lexicon", output.json); + insta::assert_debug_snapshot!("warnings", formatted_warnings); + }); Ok(()) } diff --git a/tests/diagnostics/undefined_reference/errors@undefined_reference.snap b/tests/diagnostics/undefined_reference/errors@undefined_reference.snap new file mode 100644 index 0000000..fd450fd --- /dev/null +++ b/tests/diagnostics/undefined_reference/errors@undefined_reference.snap @@ -0,0 +1,7 @@ +--- +source: tests/diagnostics_integration.rs +expression: errors_in_module +--- +[ + "UndefinedReference { name: \"User\", span: Span { start: 27, end: 31 }, module_namespace: \"test.post\" }", +] diff --git a/tests/diagnostics/undefined_reference/expected.json b/tests/diagnostics/undefined_reference/expected.json deleted file mode 100644 index 840b6e5..0000000 --- a/tests/diagnostics/undefined_reference/expected.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "error_count": 1, - "errors": [ - { - "code": "mlf::undefined_reference", - "message": "UndefinedReference" - } - ] -} diff --git a/tests/diagnostics_integration.rs b/tests/diagnostics_integration.rs index 6667d1a..edbf3f2 100644 --- a/tests/diagnostics_integration.rs +++ b/tests/diagnostics_integration.rs @@ -1,40 +1,26 @@ // Workspace-level integration tests for diagnostics. -// Each subdirectory of `diagnostics/` is a fixture with `input.mlf`, -// `expected.json`, and optionally `test.toml`. +// +// Each subdirectory of `diagnostics/` is a fixture with `input.mlf` +// and `test.toml`. Expected diagnostics are captured as an insta +// `.snap` file checked into the fixture directory. +// +// To update snapshots after an intentional change: +// just test-diagnostics # produces .snap.new files +// just review # interactive accept/reject use mlf_diagnostics::{get_error_module_namespace_str, ValidationDiagnostic}; use mlf_integration_tests::test_utils; use mlf_lang::{parser::parse_lexicon, Workspace}; -use serde::Deserialize; use std::fs; use std::path::Path; -#[derive(Debug, Deserialize)] -struct ExpectedDiagnostic { - error_count: usize, - errors: Vec, -} - -#[derive(Debug, Deserialize)] -struct ExpectedError { - #[allow(dead_code)] - code: String, - message: String, - #[serde(default)] - #[allow(dead_code)] - span: Option, -} - -#[derive(Debug, Deserialize)] -struct ExpectedSpan { - #[allow(dead_code)] - start: usize, - #[allow(dead_code)] - end: usize, -} - fn run_diagnostics_test(input_path: &Path) -> datatest_stable::Result<()> { let test_dir = input_path.parent().ok_or("input.mlf has no parent directory")?; + let test_name = test_dir + .file_name() + .and_then(|s| s.to_str()) + .ok_or("input.mlf parent has no name")? + .to_string(); let config = test_utils::load_test_config(test_dir, |name| format!("test.{}", name)) .map_err(|e| format!("{}", e))?; @@ -56,8 +42,7 @@ fn run_diagnostics_test(input_path: &Path) -> datatest_stable::Result<()> { Err(errors) => errors, }; - // Construct the diagnostic to ensure the wiring still works, even if we - // don't assert on its rendered form here. + // Construct the diagnostic to verify the wiring compiles and runs. let _diagnostic = ValidationDiagnostic::new( "input.mlf".to_string(), input.clone(), @@ -65,40 +50,20 @@ fn run_diagnostics_test(input_path: &Path) -> datatest_stable::Result<()> { validation_errors.clone(), ); - let expected_str = fs::read_to_string(test_dir.join("expected.json"))?; - let expected: ExpectedDiagnostic = serde_json::from_str(&expected_str)?; - - let errors_in_module: Vec<_> = validation_errors + let errors_in_module: Vec = validation_errors .errors .iter() .filter(|e| get_error_module_namespace_str(e) == namespace) + .map(|e| format!("{:?}", e)) .collect(); - if errors_in_module.len() != expected.error_count { - return Err(format!( - "Expected {} errors but got {}", - expected.error_count, - errors_in_module.len() - ) - .into()); - } - - for (i, expected_error) in expected.errors.iter().enumerate() { - let actual_error = errors_in_module - .get(i) - .ok_or_else(|| format!("Expected error #{} but only got {} errors", i + 1, errors_in_module.len()))?; - - let actual_message = format!("{:?}", actual_error); - if !actual_message.contains(&expected_error.message) { - return Err(format!( - "Error #{}: Expected message to contain '{}' but got: {}", - i + 1, - expected_error.message, - actual_message - ) - .into()); - } - } + insta::with_settings!({ + snapshot_path => test_dir.to_path_buf(), + prepend_module_to_snapshot => false, + snapshot_suffix => test_name, + }, { + insta::assert_debug_snapshot!("errors", errors_in_module); + }); Ok(()) } diff --git a/tests/lexicon_to_mlf/all_constraint_keys/expected.mlf b/tests/lexicon_to_mlf/all_constraint_keys/mlf@all_constraint_keys.snap similarity index 89% rename from tests/lexicon_to_mlf/all_constraint_keys/expected.mlf rename to tests/lexicon_to_mlf/all_constraint_keys/mlf@all_constraint_keys.snap index 1860573..3c7dbec 100644 --- a/tests/lexicon_to_mlf/all_constraint_keys/expected.mlf +++ b/tests/lexicon_to_mlf/all_constraint_keys/mlf@all_constraint_keys.snap @@ -1,3 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- def type version = integer constrained { minimum: 1, maximum: 10, @@ -35,4 +39,3 @@ def type digest = bytes constrained { minLength: 32, maxLength: 64, }; - diff --git a/tests/lexicon_to_mlf/all_constraint_keys/warnings@all_constraint_keys.snap b/tests/lexicon_to_mlf/all_constraint_keys/warnings@all_constraint_keys.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/all_constraint_keys/warnings@all_constraint_keys.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/array_item_constraints/expected.mlf b/tests/lexicon_to_mlf/array_item_constraints/mlf@array_item_constraints.snap similarity index 60% rename from tests/lexicon_to_mlf/array_item_constraints/expected.mlf rename to tests/lexicon_to_mlf/array_item_constraints/mlf@array_item_constraints.snap index 6e17f64..65f6917 100644 --- a/tests/lexicon_to_mlf/array_item_constraints/expected.mlf +++ b/tests/lexicon_to_mlf/array_item_constraints/mlf@array_item_constraints.snap @@ -1,7 +1,10 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- def type tags = (string constrained { minLength: 1, maxLength: 200, })[] constrained { maxLength: 20, }; - diff --git a/tests/lexicon_to_mlf/array_item_constraints/warnings@array_item_constraints.snap b/tests/lexicon_to_mlf/array_item_constraints/warnings@array_item_constraints.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/array_item_constraints/warnings@array_item_constraints.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/array_union_items/expected.mlf b/tests/lexicon_to_mlf/array_union_items/expected.mlf deleted file mode 100644 index 7a2a7c4..0000000 --- a/tests/lexicon_to_mlf/array_union_items/expected.mlf +++ /dev/null @@ -1,2 +0,0 @@ -def type feed = (post | repost)[]; - diff --git a/tests/lexicon_to_mlf/array_union_items/mlf@array_union_items.snap b/tests/lexicon_to_mlf/array_union_items/mlf@array_union_items.snap new file mode 100644 index 0000000..e9153c0 --- /dev/null +++ b/tests/lexicon_to_mlf/array_union_items/mlf@array_union_items.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- +def type feed = (post | repost)[]; diff --git a/tests/lexicon_to_mlf/array_union_items/warnings@array_union_items.snap b/tests/lexicon_to_mlf/array_union_items/warnings@array_union_items.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/array_union_items/warnings@array_union_items.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/item_extensions/expected.mlf b/tests/lexicon_to_mlf/item_extensions/mlf@item_extensions.snap similarity index 58% rename from tests/lexicon_to_mlf/item_extensions/expected.mlf rename to tests/lexicon_to_mlf/item_extensions/mlf@item_extensions.snap index d07cc1c..2ce06ec 100644 --- a/tests/lexicon_to_mlf/item_extensions/expected.mlf +++ b/tests/lexicon_to_mlf/item_extensions/mlf@item_extensions.snap @@ -1,7 +1,10 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- @main @const("x-deprecated", true) @const("x-since", "2024-01-01") record itemext { name!: string, } - diff --git a/tests/lexicon_to_mlf/item_extensions/warnings@item_extensions.snap b/tests/lexicon_to_mlf/item_extensions/warnings@item_extensions.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/item_extensions/warnings@item_extensions.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/lenient_array_without_items/expected.mlf b/tests/lexicon_to_mlf/lenient_array_without_items/expected.mlf deleted file mode 100644 index dba5d2f..0000000 --- a/tests/lexicon_to_mlf/lenient_array_without_items/expected.mlf +++ /dev/null @@ -1,2 +0,0 @@ -def type tags = unknown[]; - diff --git a/tests/lexicon_to_mlf/lenient_array_without_items/expected_warnings.txt b/tests/lexicon_to_mlf/lenient_array_without_items/expected_warnings.txt deleted file mode 100644 index 3540c6e..0000000 --- a/tests/lexicon_to_mlf/lenient_array_without_items/expected_warnings.txt +++ /dev/null @@ -1 +0,0 @@ -com.example.arraynoitems: array type is missing `items` field; treating item type as `unknown` (ATProto spec lists `items` as required) diff --git a/tests/lexicon_to_mlf/lenient_array_without_items/mlf@lenient_array_without_items.snap b/tests/lexicon_to_mlf/lenient_array_without_items/mlf@lenient_array_without_items.snap new file mode 100644 index 0000000..bc3d06e --- /dev/null +++ b/tests/lexicon_to_mlf/lenient_array_without_items/mlf@lenient_array_without_items.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- +def type tags = unknown[]; diff --git a/tests/lexicon_to_mlf/lenient_array_without_items/warnings@lenient_array_without_items.snap b/tests/lexicon_to_mlf/lenient_array_without_items/warnings@lenient_array_without_items.snap new file mode 100644 index 0000000..23498e4 --- /dev/null +++ b/tests/lexicon_to_mlf/lenient_array_without_items/warnings@lenient_array_without_items.snap @@ -0,0 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[ + "com.example.arraynoitems: array type is missing `items` field; treating item type as `unknown` (ATProto spec lists `items` as required)", +] diff --git a/tests/lexicon_to_mlf/lenient_empty_union_refs/expected.mlf b/tests/lexicon_to_mlf/lenient_empty_union_refs/expected.mlf deleted file mode 100644 index 38badcd..0000000 --- a/tests/lexicon_to_mlf/lenient_empty_union_refs/expected.mlf +++ /dev/null @@ -1,2 +0,0 @@ -def type items = unknown[]; - diff --git a/tests/lexicon_to_mlf/lenient_empty_union_refs/expected_warnings.txt b/tests/lexicon_to_mlf/lenient_empty_union_refs/expected_warnings.txt deleted file mode 100644 index 1696f44..0000000 --- a/tests/lexicon_to_mlf/lenient_empty_union_refs/expected_warnings.txt +++ /dev/null @@ -1 +0,0 @@ -com.example.emptyunion: open union has no `refs`; emitting `unknown` as a placeholder (ATProto spec lists `refs` as required on union types) diff --git a/tests/lexicon_to_mlf/lenient_empty_union_refs/mlf@lenient_empty_union_refs.snap b/tests/lexicon_to_mlf/lenient_empty_union_refs/mlf@lenient_empty_union_refs.snap new file mode 100644 index 0000000..ca2d220 --- /dev/null +++ b/tests/lexicon_to_mlf/lenient_empty_union_refs/mlf@lenient_empty_union_refs.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- +def type items = unknown[]; diff --git a/tests/lexicon_to_mlf/lenient_empty_union_refs/warnings@lenient_empty_union_refs.snap b/tests/lexicon_to_mlf/lenient_empty_union_refs/warnings@lenient_empty_union_refs.snap new file mode 100644 index 0000000..8dbe9f1 --- /dev/null +++ b/tests/lexicon_to_mlf/lenient_empty_union_refs/warnings@lenient_empty_union_refs.snap @@ -0,0 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[ + "com.example.emptyunion: open union has no `refs`; emitting `unknown` as a placeholder (ATProto spec lists `refs` as required on union types)", +] diff --git a/tests/lexicon_to_mlf/lenient_object_without_properties/expected.mlf b/tests/lexicon_to_mlf/lenient_object_without_properties/expected.mlf deleted file mode 100644 index 0c4c112..0000000 --- a/tests/lexicon_to_mlf/lenient_object_without_properties/expected.mlf +++ /dev/null @@ -1,4 +0,0 @@ -/// Marker for bold text -def type bold = { -}; - diff --git a/tests/lexicon_to_mlf/lenient_object_without_properties/expected_warnings.txt b/tests/lexicon_to_mlf/lenient_object_without_properties/expected_warnings.txt deleted file mode 100644 index dd12b80..0000000 --- a/tests/lexicon_to_mlf/lenient_object_without_properties/expected_warnings.txt +++ /dev/null @@ -1 +0,0 @@ -com.example.marker: object type is missing `properties` field; treating as empty (ATProto spec lists `properties` as required) diff --git a/tests/lexicon_to_mlf/lenient_object_without_properties/mlf@lenient_object_without_properties.snap b/tests/lexicon_to_mlf/lenient_object_without_properties/mlf@lenient_object_without_properties.snap new file mode 100644 index 0000000..68bbf11 --- /dev/null +++ b/tests/lexicon_to_mlf/lenient_object_without_properties/mlf@lenient_object_without_properties.snap @@ -0,0 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- +/// Marker for bold text +def type bold = { +}; diff --git a/tests/lexicon_to_mlf/lenient_object_without_properties/warnings@lenient_object_without_properties.snap b/tests/lexicon_to_mlf/lenient_object_without_properties/warnings@lenient_object_without_properties.snap new file mode 100644 index 0000000..d500f8b --- /dev/null +++ b/tests/lexicon_to_mlf/lenient_object_without_properties/warnings@lenient_object_without_properties.snap @@ -0,0 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[ + "com.example.marker: object type is missing `properties` field; treating as empty (ATProto spec lists `properties` as required)", +] diff --git a/tests/lexicon_to_mlf/main_renamed_local_ref/expected.mlf b/tests/lexicon_to_mlf/main_renamed_local_ref/mlf@main_renamed_local_ref.snap similarity index 56% rename from tests/lexicon_to_mlf/main_renamed_local_ref/expected.mlf rename to tests/lexicon_to_mlf/main_renamed_local_ref/mlf@main_renamed_local_ref.snap index 2875419..074b08d 100644 --- a/tests/lexicon_to_mlf/main_renamed_local_ref/expected.mlf +++ b/tests/lexicon_to_mlf/main_renamed_local_ref/mlf@main_renamed_local_ref.snap @@ -1,3 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- @main def type thing = { value!: string, @@ -6,4 +10,3 @@ def type thing = { def type container = { items!: thing[], }; - diff --git a/tests/lexicon_to_mlf/main_renamed_local_ref/warnings@main_renamed_local_ref.snap b/tests/lexicon_to_mlf/main_renamed_local_ref/warnings@main_renamed_local_ref.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/main_renamed_local_ref/warnings@main_renamed_local_ref.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/nullable_fields/expected.mlf b/tests/lexicon_to_mlf/nullable_fields/mlf@nullable_fields.snap similarity index 74% rename from tests/lexicon_to_mlf/nullable_fields/expected.mlf rename to tests/lexicon_to_mlf/nullable_fields/mlf@nullable_fields.snap index 66a6a71..38f2d8f 100644 --- a/tests/lexicon_to_mlf/nullable_fields/expected.mlf +++ b/tests/lexicon_to_mlf/nullable_fields/mlf@nullable_fields.snap @@ -1,3 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- @main record profile { handle!: string, @@ -11,4 +15,3 @@ def type settings = { theme!: string, accent: string | null, }; - diff --git a/tests/lexicon_to_mlf/nullable_fields/warnings@nullable_fields.snap b/tests/lexicon_to_mlf/nullable_fields/warnings@nullable_fields.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/nullable_fields/warnings@nullable_fields.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/ref_hint_warning/expected_warnings.txt b/tests/lexicon_to_mlf/ref_hint_warning/expected_warnings.txt deleted file mode 100644 index de41d1a..0000000 --- a/tests/lexicon_to_mlf/ref_hint_warning/expected_warnings.txt +++ /dev/null @@ -1 +0,0 @@ -com.example.refhint: extension field "xFallback" has value "com.example.other#someType" which looks NSID-shaped; emitted as `@const` — consider `@reference` if you intend workspace name resolution when hand-editing the MLF diff --git a/tests/lexicon_to_mlf/ref_hint_warning/expected.mlf b/tests/lexicon_to_mlf/ref_hint_warning/mlf@ref_hint_warning.snap similarity index 56% rename from tests/lexicon_to_mlf/ref_hint_warning/expected.mlf rename to tests/lexicon_to_mlf/ref_hint_warning/mlf@ref_hint_warning.snap index 2c65f0b..f33220c 100644 --- a/tests/lexicon_to_mlf/ref_hint_warning/expected.mlf +++ b/tests/lexicon_to_mlf/ref_hint_warning/mlf@ref_hint_warning.snap @@ -1,6 +1,9 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- @main @const("xFallback", "com.example.other#someType") def type refhint = { value: string, }; - diff --git a/tests/lexicon_to_mlf/ref_hint_warning/warnings@ref_hint_warning.snap b/tests/lexicon_to_mlf/ref_hint_warning/warnings@ref_hint_warning.snap new file mode 100644 index 0000000..3dd0581 --- /dev/null +++ b/tests/lexicon_to_mlf/ref_hint_warning/warnings@ref_hint_warning.snap @@ -0,0 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[ + "com.example.refhint: extension field \"xFallback\" has value \"com.example.other#someType\" which looks NSID-shaped; emitted as `@const` — consider `@reference` if you intend workspace name resolution when hand-editing the MLF", +] diff --git a/tests/lexicon_to_mlf/self_top_level_description/expected.mlf b/tests/lexicon_to_mlf/self_top_level_description/mlf@self_top_level_description.snap similarity index 58% rename from tests/lexicon_to_mlf/self_top_level_description/expected.mlf rename to tests/lexicon_to_mlf/self_top_level_description/mlf@self_top_level_description.snap index 57d6587..98e4829 100644 --- a/tests/lexicon_to_mlf/self_top_level_description/expected.mlf +++ b/tests/lexicon_to_mlf/self_top_level_description/mlf@self_top_level_description.snap @@ -1,3 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- /// A short description of the whole lexicon. self {} @@ -5,4 +9,3 @@ self {} def type described = { name!: string, }; - diff --git a/tests/lexicon_to_mlf/self_top_level_description/warnings@self_top_level_description.snap b/tests/lexicon_to_mlf/self_top_level_description/warnings@self_top_level_description.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/self_top_level_description/warnings@self_top_level_description.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/self_top_level_extensions/expected.mlf b/tests/lexicon_to_mlf/self_top_level_extensions/mlf@self_top_level_extensions.snap similarity index 69% rename from tests/lexicon_to_mlf/self_top_level_extensions/expected.mlf rename to tests/lexicon_to_mlf/self_top_level_extensions/mlf@self_top_level_extensions.snap index b1f1049..8934fc3 100644 --- a/tests/lexicon_to_mlf/self_top_level_extensions/expected.mlf +++ b/tests/lexicon_to_mlf/self_top_level_extensions/mlf@self_top_level_extensions.snap @@ -1,3 +1,7 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- /// With extensions. @const("revision", 3) @const("x-vendor-flag", true) @@ -8,4 +12,3 @@ self {} def type extended = { value: string, }; - diff --git a/tests/lexicon_to_mlf/self_top_level_extensions/warnings@self_top_level_extensions.snap b/tests/lexicon_to_mlf/self_top_level_extensions/warnings@self_top_level_extensions.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/self_top_level_extensions/warnings@self_top_level_extensions.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf/unknown_def_type_passthrough/expected.mlf b/tests/lexicon_to_mlf/unknown_def_type_passthrough/mlf@unknown_def_type_passthrough.snap similarity index 78% rename from tests/lexicon_to_mlf/unknown_def_type_passthrough/expected.mlf rename to tests/lexicon_to_mlf/unknown_def_type_passthrough/mlf@unknown_def_type_passthrough.snap index 98d53be..01aac86 100644 --- a/tests/lexicon_to_mlf/unknown_def_type_passthrough/expected.mlf +++ b/tests/lexicon_to_mlf/unknown_def_type_passthrough/mlf@unknown_def_type_passthrough.snap @@ -1,7 +1,10 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: output.mlf +--- @main @const("type", "permission-set") @const("title", "Example Access") @const("detail", "Access to example resources.") @const("permissions", [{ "type": "permission", "resource": "repo", "collection": ["com.example.one", "com.example.two"] }]) def type permission = unknown; - diff --git a/tests/lexicon_to_mlf/unknown_def_type_passthrough/warnings@unknown_def_type_passthrough.snap b/tests/lexicon_to_mlf/unknown_def_type_passthrough/warnings@unknown_def_type_passthrough.snap new file mode 100644 index 0000000..87c353b --- /dev/null +++ b/tests/lexicon_to_mlf/unknown_def_type_passthrough/warnings@unknown_def_type_passthrough.snap @@ -0,0 +1,5 @@ +--- +source: tests/lexicon_to_mlf_integration.rs +expression: formatted_warnings +--- +[] diff --git a/tests/lexicon_to_mlf_integration.rs b/tests/lexicon_to_mlf_integration.rs index 51728c4..4d4ebe3 100644 --- a/tests/lexicon_to_mlf_integration.rs +++ b/tests/lexicon_to_mlf_integration.rs @@ -1,11 +1,12 @@ // Workspace-level integration tests for Lexicon JSON → MLF conversion. // -// Each subdirectory of `lexicon_to_mlf/` is a single test case containing: -// - `input.json`: the Lexicon JSON to convert -// - `expected.mlf`: the expected MLF source produced by the converter -// - `expected_warnings.txt` (optional): the expected warnings, one -// per line in `: ` form. When absent, the test -// asserts no warnings were produced. +// Each subdirectory of `lexicon_to_mlf/` is a test case with an +// `input.json`. Expected output (MLF source + converter warnings) is +// captured as insta `.snap` files checked into the fixture directory. +// +// To update snapshots after an intentional change: +// just test-lexicon-to-mlf # produces .snap.new files +// just review # interactive accept/reject use mlf_cli::generate::mlf::generate_mlf_from_json; use serde_json::Value; @@ -14,45 +15,31 @@ use std::path::Path; fn run_case(input_path: &Path) -> datatest_stable::Result<()> { let test_dir = input_path.parent().ok_or("input.json has no parent directory")?; + let test_name = test_dir + .file_name() + .and_then(|s| s.to_str()) + .ok_or("input.json parent has no name")? + .to_string(); let input = fs::read_to_string(input_path)?; let json: Value = serde_json::from_str(&input)?; let output = generate_mlf_from_json(&json).map_err(|e| format!("{:?}", e))?; - let expected = fs::read_to_string(test_dir.join("expected.mlf"))?; - if output.mlf != expected { - return Err(format!( - "MLF mismatch:\n--- expected ---\n{}\n--- got ---\n{}", - expected, output.mlf - ) - .into()); - } - - let warnings_path = test_dir.join("expected_warnings.txt"); - let expected_warnings = if warnings_path.exists() { - fs::read_to_string(&warnings_path)? - } else { - String::new() - }; - let actual_warnings = output + let formatted_warnings: Vec = output .warnings .iter() .map(|w| format!("{}: {}", w.namespace, w.message)) - .collect::>() - .join("\n"); - let actual_warnings = if actual_warnings.is_empty() { - String::new() - } else { - format!("{}\n", actual_warnings) - }; - if actual_warnings != expected_warnings { - return Err(format!( - "Warnings mismatch:\n--- expected ---\n{}\n--- got ---\n{}", - expected_warnings, actual_warnings - ) - .into()); - } + .collect(); + + insta::with_settings!({ + snapshot_path => test_dir.to_path_buf(), + prepend_module_to_snapshot => false, + snapshot_suffix => test_name, + }, { + insta::assert_snapshot!("mlf", output.mlf); + insta::assert_debug_snapshot!("warnings", formatted_warnings); + }); Ok(()) }