diff options
Add bisect_ppx test coverage infrastructure with CI workflow and test suites
Integrate bisect_ppx for code coverage across the test suite:
- Add bisect_ppx instrumentation to lib/dune and test/dune
- Add bisect_ppx dependency to dune-project, nixtamal.opam, and nix/package/nixtamal.nix
- Create bisect.yml configuration for HTML and text coverage reports
- Add .github/workflows/coverage.yml for CI-based coverage reporting
- Fix flake.nix devShell to include checkInputs for full development environment
- Add coverage checks to flake.nix checks output
New test suites for recently ported features:
- test/test_upgrade.ml: Tests for schema upgrade command (backup, dry-run, version validation)
- test/test_fossil.ml: Tests for Fossil VCS codec and lockfile roundtrips
- test/test_lockfile.ml: Tests for lockfile auto-creation and serialization
- test/test_main.ml: Register all new test suites
Documentation updates:
- AGENTS.md: Add contact info (website, XMPP MUC), note llm/ folder is gitignored
- README.asciidoc: Add website link, mention Fossil VCS, schema versioning, upgrade command
- .gitignore: Add _build/ and _coverage/ directories
Covers testing for previously ported features: schema upgrade, Fossil VCS support,
and lockfile auto-creation.
Diffstat (limited to 'test/test_upgrade.ml')
| -rw-r--r-- | test/test_upgrade.ml | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/test/test_upgrade.ml b/test/test_upgrade.ml new file mode 100644 index 0000000..c10c647 --- /dev/null +++ b/test/test_upgrade.ml @@ -0,0 +1,113 @@ +(*─────────────────────────────────────────────────────────────────────────────┐ +│ SPDX-FileCopyrightText: 2026 toastal <https://toast.al/contact/> │ +│ SPDX-License-Identifier: LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception │ +└─────────────────────────────────────────────────────────────────────────────*) +open Alcotest +open Nixtamal + +let write_file path content = + Eio.Path.with_open_out ~create:(`Or_truncate 0o644) path @@ fun flow -> + Eio.Flow.copy_string content flow + +let read_file path = + Eio.Path.with_open_in path @@ fun flow -> + let buf = Eio.Buf_read.of_flow flow ~max_size: max_int in + Eio.Buf_read.take_all buf + +let has_substring haystack needle = + try + ignore (Str.search_forward (Str.regexp_string needle) haystack 0); + true + with Not_found -> false + +let setup_workdir ~env name = + let cwd = Eio.Stdenv.cwd env in + let dir = Eio.Path.(cwd / "_build" / "tests" / name) in + ( + match Eio.Path.kind ~follow:true dir with + | `Not_found -> () + | _ -> Eio.Path.rmtree dir + ); + Eio.Path.mkdirs ~perm:0o755 dir; + Working_directory.set ~directory:dir; + dir + +let write_minimal_manifest ~version dir = + let content = Fmt.str {|version "%s" +inputs {} +|} version in + write_file Eio.Path.(dir / Manifest.filename) content + +let write_minimal_lockfile ~version dir = + let content = Fmt.str {|{"v":"%s","i":{}}|} version in + write_file Eio.Path.(dir / Lockfile.filename) content + +let suite = + [ + test_case "Upgrade backup path naming" `Quick (fun () -> + check string "backup extension" "manifest.kdl.bak" (backup_path "manifest.kdl") + ); + test_case "Upgrade dry-run keeps files untouched" `Quick (fun () -> + Eio_main.run @@ fun env -> + let dir = setup_workdir ~env "upgrade-dry-run" in + write_minimal_manifest ~version:"0.1.1" dir; + write_minimal_lockfile ~version:"0.1.1" dir; + Lockfile.lockfile := None; + let res = upgrade ~to_:Schema.Version.V0_2_0 ~dry_run:true () in + check bool "dry-run succeeds" true (Result.is_ok res); + let manifest_content = read_file Eio.Path.(dir / Manifest.filename) in + check bool "manifest stays old version" true (has_substring manifest_content "0.1.1"); + check bool "manifest backup not created" false (Eio.Path.is_file Eio.Path.(dir / backup_path Manifest.filename)); + check bool "lock backup not created" false (Eio.Path.is_file Eio.Path.(dir / backup_path Lockfile.filename)) + ); + test_case "Upgrade rewrites versions and cleans backups" `Quick (fun () -> + Eio_main.run @@ fun env -> + let dir = setup_workdir ~env "upgrade-success" in + write_minimal_manifest ~version:"0.1.1" dir; + write_minimal_lockfile ~version:"0.1.1" dir; + Lockfile.lockfile := None; + let res = upgrade ~to_:Schema.Version.V0_2_0 () in + check bool "upgrade succeeds" true (Result.is_ok res); + let manifest_content = read_file Eio.Path.(dir / Manifest.filename) in + check bool "manifest version upgraded" true (has_substring manifest_content "0.2.0"); + let lock_version = + match Lockfile.read () with + | Ok (Some lock) -> lock.version + | Ok None -> fail "Lockfile missing after upgrade" + | Error err -> failf "Failed to read lockfile: %s" err + in + check string "lockfile version upgraded" "0.2.0" lock_version; + check bool "manifest backup cleaned" false (Eio.Path.is_file Eio.Path.(dir / backup_path Manifest.filename)) + ); + test_case "Upgrade aborts on version mismatch" `Quick (fun () -> + Eio_main.run @@ fun env -> + let dir = setup_workdir ~env "upgrade-version-mismatch" in + write_minimal_manifest ~version:"0.1.1" dir; + write_minimal_lockfile ~version:"0.1.1" dir; + Lockfile.lockfile := None; + let res = upgrade ~from:Schema.Version.V0_2_0 ~to_:Schema.Version.V0_2_0 () in + check bool "upgrade fails" true (Result.is_error res); + let lock_version = + match Lockfile.read () with + | Ok (Some lock) -> lock.version + | _ -> fail "Lockfile should still be present after abort" + in + check string "lockfile unchanged" "0.1.1" lock_version; + check bool "manifest backup not created" false (Eio.Path.is_file Eio.Path.(dir / backup_path Manifest.filename)); + check bool "lockfile backup not created" false (Eio.Path.is_file Eio.Path.(dir / backup_path Lockfile.filename)) + ); + test_case "Upgrade detects unknown manifest version" `Quick (fun () -> + Eio_main.run @@ fun env -> + let dir = setup_workdir ~env "upgrade-version-detect" in + write_minimal_manifest ~version:"9.9.9" dir; + write_minimal_lockfile ~version:"0.1.1" dir; + Lockfile.lockfile := None; + let res = upgrade () in + check bool + "unknown schema version is rejected" + true + (match res with + | Error (`Upgrade msg) -> String.length msg > 0 + | _ -> false) + ); + ] |
