test: contract tests improving coverage (toward #79)#81
Conversation
…uting Toward harmont-dev#79 (>=95% coverage). Each addition targets a real Contract path — a wire-format conversion, a gRPC status classification, or a refusal — not a getter or a pass-through. Triage per AGENTS.md: grpc/codec.ex 54% -> 100% from_grpc/to_grpc wire contracts + the rpc_error status-classification table (every domain reason -> its promised gRPC status). cfg/network.ex 86% -> 100% non-string network.uplink -> ArgumentError. cfg/grpc.ex 89% -> 100% plaintext-default cred(nil) via load/0. cluster/routing 85% -> 100% register_self already-registered + all/0 node mapping (new test file; reuses the named singleton to avoid a start_supervised race). cfg/budget.ex 80% -> 90% non-number / bad unit string / wrong type -> specific error tuples (table-driven). redist/file.ex 87% -> 93% {:install_failed, _} on an unwritable dest. redist/targz.ex 88% -> 94% {:extract_failed, _} on a corrupt archive with a valid checksum. Deliberately declined (mechanical / privileged), with rationale recorded: - redist download_error Req pass-throughs (already declined in moduledocs); - cfg/budget.get/0 getter and the dead required/3 :error branches (a default is always present, so they are unreachable); - cfg/toml.ex /sys/fs/cgroup /etc/subuid /etc/subgid (need Linux root/cgroupfs); - node/fire_vmm/{meter,daemon,relay,guest_agent} (integration-only: cgroup, Firecracker, sockets, built musl agent — covered in CI's integration job). All +20 tests pass; mix format --check-formatted and mix credo --strict clean.
| test "with no cred configured, load returns nil (plaintext)" do | ||
| # The plaintext default: an absent cred must yield nil, never a defaulted | ||
| # credential that would silently enable TLS. | ||
| assert Grpc.load().cred == nil | ||
| end |
| # `from_grpc/1` -- the decode boundary. The contract: an inbound CreateVmRequest | ||
| # maps to a domain Spec with the right field placement, or is refused with a | ||
| # specific reason the server then classifies to a gRPC status. | ||
| describe "from_grpc/1 CreateVmRequest" do | ||
| test "a well-formed request decodes to a Spec preserving every field" do | ||
| assert {:ok, | ||
| %Spec{ | ||
| img_id: "img", | ||
| type: :micro, | ||
| arch: :aarch64, | ||
| boot_args: "console=ttyS0" | ||
| }} = | ||
| Codec.from_grpc(%CreateVmRequest{ | ||
| img_id: "img", | ||
| instance_type: :INSTANCE_TYPE_MICRO, | ||
| arch: :ARCHITECTURE_AARCH64, | ||
| boot_args: "console=ttyS0" | ||
| }) | ||
| end |
There was a problem hiding this comment.
I generally don't see that much utility in these tests frankly. I will be honest I mostly vibe-coded these tests, so that's how we ended up with codec_test.exs, but in the future I'd like to see these tests replaced with e2e grpc tests.
i'm comfortable with your PR, considering the existing precedent =)
There was a problem hiding this comment.
Agreed, more following through on the issue's ask. I'l look into creating an e2e ask and see if I can help with that.
| test "install/3 surfaces a corrupt archive as {:extract_failed, _}", %{ | ||
| server: server, | ||
| dest: dest | ||
| } do |
| # Under `mix test --no-start` the app tree (which provisions Routing) is | ||
| # absent; start a test-scoped registry so these contracts are testable | ||
| # without the full supervision tree. Routing is a named singleton, so if | ||
| # another test (e.g. HyperTest) already started it, reuse that one rather | ||
| # than racing on start_supervised!/1. | ||
| if Process.whereis(Routing) do | ||
| :ok | ||
| else | ||
| _ = Application.ensure_all_started(:horde) | ||
|
|
||
| case start_supervised(Routing) do | ||
| {:ok, _pid} -> :ok | ||
| # Lost the start race to a parallel test: reuse the existing registry. | ||
| {:error, {:already_started, _pid}} -> :ok | ||
| end | ||
| end |
There was a problem hiding this comment.
my gut feeling is that this is going to cause some flaky tests in the future, but OK, good enough for now
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
thank you for your PR! |
Toward #79 (>=95% coverage).
What
Each addition targets a Contract path — a wire-format conversion, a gRPC status classification, or a refusal — not a getter or a pass-through. Triage per
AGENTS.md(Contract / Mechanical / Privileged):hyper/grpc/codec.exfrom_grpc/to_grpcwire contracts + therpc_errorstatus-classification table (every domain reason → its promised gRPC status)hyper/cfg/network.exnetwork.uplink→ArgumentErrorhyper/cfg/grpc.excred(nil)viaload/0hyper/cluster/routing.exregister_selfalready-registered +all/0node mapping (new test file; reuses the named singleton to avoid astart_supervisedrace)hyper/cfg/budget.exredist/file.ex{:install_failed, _}on an unwritable destredist/targz.ex{:extract_failed, _}on a corrupt archive with a valid checksum+20 tests, table-driven where the assertion shape repeats.
mix format --check-formattedandmix credo --strictare clean on all of them.Deliberately declined
Per
AGENTS.md's "Answering coverage reports" — these gaps are not worth a unit test, with rationale:redistdownload_errorReq pass-throughs (already declined in those modules' own moduledocs);cfg/budget.get/0getter;cfg/budget'srequired/3:errorbranches (a default is always present, so they are unreachable).cfg/toml.ex(needs root to write/etc/hyper/config.toml; the absent-file-> %{}default is covered),sys/linux/cgroup/v2.ex(cgroupfs),sys/linux/subid.ex(/etc/subuid+/etc/subgid; theranges/1parsing is fixture-tested), andnode/fire_vmm/{meter,daemon,relay,guest_agent}(need live cgroup / Firecracker / sockets / the built musl agent — covered in CI'sintegrationjob).A patch may legitimately merge below 100%; these uncovered mechanical lines cost less than tests that pin implementation detail.
Notes
RoutingTeststarts the registry withstart_supervised(non-bang) and reuses an already-started one, sinceHyper.Cluster.Routingis a named singleton andHyperTestalso starts it — a bang call raced underasync.GLM-5.2 did this work.
Closes toward #79.