From dbee62e6d9331686499297d65ab34971f2e2997e Mon Sep 17 00:00:00 2001 From: Kaylee Lubick Date: Tue, 20 Oct 2026 14:32:12 -0400 Subject: [PATCH 1/4] Rewrite Go test tables as named subtests Signed-off-by: Kaylee Lubick --- scripts/collectlogs/collectlogs_test.go | 33 ++- .../inference-dispatcher/dispatcher_test.go | 24 +- services/eap-noob/eapnoob_test.go | 20 +- .../nvpair-cluster-manager/cm_unit_test.go | 20 +- .../nvpair-cluster-manager/fsutil_test.go | 13 +- .../pairing_fail_test.go | 21 +- .../removal_proof_test.go | 22 +- .../self_remove_boundary_test.go | 90 +++----- .../self_remove_test.go | 120 +++------- .../nvpair-engine-manager/catalog_test.go | 48 ++-- .../nvpair-engine-manager/executor_test.go | 16 +- .../launch_controls_test.go | 132 ++++++----- services/nvpair-engine-manager/launch_test.go | 14 +- .../lifecycle_stop_test.go | 20 +- .../lifecycle_stop_unix_test.go | 82 ++++--- .../nvpair-engine-manager/lmsdelete_test.go | 21 +- services/nvpair-engine-manager/lmsget_test.go | 148 +++++------- .../nvpair-engine-manager/manager_test.go | 63 ++--- .../nvpair-engine-manager/models_gate_test.go | 46 ++-- services/nvpair-engine-manager/models_test.go | 75 +++--- .../nvpair-engine-manager/modelsdir_test.go | 61 +++-- .../portoverride_test.go | 18 +- services/nvpair-engine-manager/proc_test.go | 21 +- .../pull_llamacpp_test.go | 36 ++- services/nvpair-engine-manager/pull_test.go | 20 +- .../nvpair-engine-manager/registry_test.go | 17 +- .../nvpair-engine-manager/remediation_test.go | 28 +-- .../remoteclient_test.go | 26 +-- .../nvpair-engine-manager/setport_test.go | 40 ++-- .../nvpair-engine-manager/settings_test.go | 62 +++-- .../nvpair-engine-manager/usererrors_test.go | 23 +- services/nvpair-errors/peersync_test.go | 36 ++- .../nvpair-job-scheduler/delivery_test.go | 8 +- services/nvpair-manual-nodes/manager_test.go | 19 +- services/nvpair-node-info/gpu_windows_test.go | 43 ++-- .../nvpair-node-info/stats_darwin_test.go | 23 +- services/nvpair-node-info/stats_linux_test.go | 218 ++++-------------- services/nvpair-node-info/stats_test.go | 107 +++------ .../nvpair-node-info/stats_windows_test.go | 17 +- .../nvpair-node-scanner/ghost_node_test.go | 34 +-- .../nvpair-node-scanner/telemetry_test.go | 31 ++- services/nvpair-node-scanner/tls_test.go | 28 +-- services/nvpair-node-settings/manager_test.go | 62 ++--- services/nvpair-proxy/alias_test.go | 44 ++-- services/nvpair-proxy/engines_test.go | 52 ++--- services/nvpair-proxy/ingress_test.go | 22 +- services/nvpair-proxy/retry_test.go | 26 +-- services/nvpair-proxy/twofacade_test.go | 21 +- services/nvpair-tui/ui/demo_test.go | 40 ++-- services/nvpair-tui/ui/engineswire_test.go | 54 +++-- services/nvpair-tui/ui/framebudget_test.go | 115 ++++----- services/nvpair-tui/ui/jobs_test.go | 10 +- services/nvpair-tui/ui/model_test.go | 31 ++- services/nvpair-tui/ui/nodedetail_test.go | 149 +++++------- services/nvpair-tui/ui/nodesmodel_test.go | 47 ++-- services/nvpair-tui/ui/nodetelemetry_test.go | 44 ++-- services/nvpair-tui/ui/service_test.go | 13 +- services/nvpair-tui/ui/styles_test.go | 47 ++-- services/nvpair-tui/ui/table_test.go | 155 +++++-------- services/nvpair-tui/ui/toast_test.go | 120 +++++----- services/nvpair-tui/ui/updatecheck_test.go | 23 +- services/nvpair-ui-broker/advertiser_test.go | 24 +- services/nvpair-ui-broker/discovery_test.go | 22 +- .../nvpair-ui-broker/enginefacade_test.go | 26 +-- services/nvpair-ui-broker/engineproxy_test.go | 116 ++++------ .../enginesettings_security_test.go | 24 +- .../nvpair-ui-broker/enginesettings_test.go | 22 +- .../nvpair-ui-broker/lmstudioport_test.go | 87 +++---- .../nvpair-ui-broker/nodeactivity_test.go | 22 +- .../nodeinfo_identity_test.go | 20 +- .../nodeinfo_observed_test.go | 23 +- services/nvpair-ui-broker/ollamahost_test.go | 85 ++----- .../nvpair-ui-broker/proxyaddressing_test.go | 106 ++------- services/nvpair-ui-broker/proxyport_test.go | 198 ++++++---------- .../nvpair-ui-broker/proxyportbump_test.go | 53 ++--- .../nvpair-ui-broker/settingsport_test.go | 17 +- services/nvpair-ui-broker/supervisor_test.go | 25 +- .../nvpair-workload-manager/workload_test.go | 37 +-- services/shared/clustertrust/mesh_test.go | 18 +- services/shared/engines/engines_test.go | 31 +-- services/shared/jsonrpc/jsonrpc_test.go | 12 +- services/shared/mdns/responder_test.go | 20 +- services/shared/netpick/netpick_test.go | 17 +- services/shared/noderec/noderec_test.go | 33 ++- services/tests/llamacpp_interop_test.go | 16 +- services/tests/model_routing_interop_test.go | 36 ++- services/tests/ollama_host_alias_test.go | 26 ++- 87 files changed, 1731 insertions(+), 2404 deletions(-) diff --git a/scripts/collectlogs/collectlogs_test.go b/scripts/collectlogs/collectlogs_test.go index 39a30387..8115cdd7 100644 --- a/scripts/collectlogs/collectlogs_test.go +++ b/scripts/collectlogs/collectlogs_test.go @@ -296,25 +296,22 @@ func TestIsHostname_RejectsNonHosts(t *testing.T) { } func TestClassifyIP_DistinguishesPrivatePublicAndLoopbackAddresses(t *testing.T) { - cases := map[string]struct { - class string - keep bool - }{ - "127.0.0.1": {"loopback", true}, - "::1": {"loopback", true}, - "0.0.0.0": {"loopback", true}, - "192.168.1.10": {"lan", false}, - "10.221.6.52": {"lan", false}, - "172.16.4.4": {"lan", false}, - "100.64.0.1": {"cgnat", false}, - "8.8.8.8": {"public", false}, - "169.254.10.10": {"link-local", true}, - } - for input, want := range cases { - class, keep := classifyIP(input) - assert.Equal(t, want.class, class, "IP %q", input) - assert.Equal(t, want.keep, keep, "IP %q", input) + test := func(name, input, wantClass string, wantKeep bool) { + t.Run(name, func(t *testing.T) { + class, keep := classifyIP(input) + assert.Equal(t, wantClass, class) + assert.Equal(t, wantKeep, keep) + }) } + test("IPv4 loopback", "127.0.0.1", "loopback", true) + test("IPv6 loopback", "::1", "loopback", true) + test("unspecified", "0.0.0.0", "loopback", true) + test("192.168 LAN", "192.168.1.10", "lan", false) + test("10 LAN", "10.221.6.52", "lan", false) + test("172.16 LAN", "172.16.4.4", "lan", false) + test("carrier-grade NAT", "100.64.0.1", "cgnat", false) + test("public", "8.8.8.8", "public", false) + test("link-local", "169.254.10.10", "link-local", true) } // Tokens must be identical across runs on the same input, otherwise two diff --git a/scripts/inference-dispatcher/dispatcher_test.go b/scripts/inference-dispatcher/dispatcher_test.go index 9afa5818..b10efd15 100644 --- a/scripts/inference-dispatcher/dispatcher_test.go +++ b/scripts/inference-dispatcher/dispatcher_test.go @@ -404,19 +404,19 @@ func TestUpstreamErrorBody_NeverReachesLogs(t *testing.T) { require.NoError(t, err, "read result log") errors, err := os.ReadFile(errorLog) require.NoError(t, err, "read error log") - for name, content := range map[string]string{ - "stdout": stdout.String(), - "stderr": stderr.String(), - "result log": string(results), - "error log": string(errors), - } { - assert.NotContains(t, content, "quarterly", "%s leaked the upstream error body", name) - assert.NotContains(t, content, echoed, "%s leaked the upstream error body", name) - if name == "stdout" { - continue - } - assert.Contains(t, content, "400 Bad Request", "%s dropped the HTTP status", name) + test := func(name, content string, wantStatus bool) { + t.Run(name, func(t *testing.T) { + assert.NotContains(t, content, "quarterly", "upstream error body must not leak") + assert.NotContains(t, content, echoed, "upstream error body must not leak") + if wantStatus { + assert.Contains(t, content, "400 Bad Request", "HTTP status must be preserved") + } + }) } + test("stdout", stdout.String(), false) + test("stderr", stderr.String(), true) + test("result log", string(results), true) + test("error log", string(errors), true) } func TestPromptDigest_DoesNotLeakPromptText(t *testing.T) { diff --git a/services/eap-noob/eapnoob_test.go b/services/eap-noob/eapnoob_test.go index 43afdb05..9ca23cd6 100644 --- a/services/eap-noob/eapnoob_test.go +++ b/services/eap-noob/eapnoob_test.go @@ -180,13 +180,17 @@ func TestOneStepKDF_DerivesRepeatableKeys(t *testing.T) { } func TestJWK_RoundTripsPublicKeys(t *testing.T) { - for _, id := range []int{1, 2} { - cs, err := suiteByID(id) - require.NoError(t, err) - priv, jwk, err := cs.generateKeypair() - require.NoError(t, err, "generate:") - pub, err := cs.decodeJWK(jwk) - require.NoError(t, err, "decode JWK:") - assert.Equal(t, priv.PublicKey().Bytes(), pub.Bytes(), "suite %d: JWK round-trip mismatch", id) + test := func(name string, id int) { + t.Run(name, func(t *testing.T) { + cs, err := suiteByID(id) + require.NoError(t, err) + priv, jwk, err := cs.generateKeypair() + require.NoError(t, err, "generate keypair") + pub, err := cs.decodeJWK(jwk) + require.NoError(t, err, "decode JWK") + assert.Equal(t, priv.PublicKey().Bytes(), pub.Bytes(), "JWK round-trip mismatch") + }) } + test("suite 1", 1) + test("suite 2", 2) } diff --git a/services/nvpair-cluster-manager/cm_unit_test.go b/services/nvpair-cluster-manager/cm_unit_test.go index 69d85b95..bac23ddb 100644 --- a/services/nvpair-cluster-manager/cm_unit_test.go +++ b/services/nvpair-cluster-manager/cm_unit_test.go @@ -35,15 +35,19 @@ func TestIdentity_RejectsReloadWhenPrivateKeyIsMissing(t *testing.T) { } func TestPIN_RoundTripsThroughNoobEncoding(t *testing.T) { - cases := []string{"000000", "000123", "402199", "999999"} - for _, pin := range cases { - noob := noobFromPIN(pin) - require.Len(t, noob, 16, "noob length") - got := new(big.Int).SetBytes(noob).String() - want := new(big.Int) - want.SetString(pin, 10) - require.Equal(t, want.String(), got, "noob decodes incorrectly") + test := func(name, pin string) { + t.Run(name, func(t *testing.T) { + noob := noobFromPIN(pin) + require.Len(t, noob, 16, "noob length") + want := new(big.Int) + want.SetString(pin, 10) + require.Equal(t, want.String(), new(big.Int).SetBytes(noob).String(), "noob decodes incorrectly") + }) } + test("all zeroes", "000000") + test("leading zeroes", "000123") + test("ordinary PIN", "402199") + test("maximum PIN", "999999") gp, noob, err := generatePIN() require.NoError(t, err, "generatePIN") diff --git a/services/nvpair-cluster-manager/fsutil_test.go b/services/nvpair-cluster-manager/fsutil_test.go index 5c1afd10..690e7062 100644 --- a/services/nvpair-cluster-manager/fsutil_test.go +++ b/services/nvpair-cluster-manager/fsutil_test.go @@ -77,8 +77,13 @@ func TestAtomicWrite_FailsImmediatelyForPermanentRenameError(t *testing.T) { } func TestIsTransientReplaceError_RecognizesRetryableRenameFailures(t *testing.T) { - require.True(t, isTransientReplaceError(errors.New("Access is denied.")), "expected Access is denied to be transient") - require.True(t, isTransientReplaceError(errors.New("The process cannot access the file because it is being used by another process.")), "expected sharing text to be transient") - require.False(t, isTransientReplaceError(errors.New("no such file or directory")), "ENOENT must not be treated as transient") - require.False(t, isTransientReplaceError(nil), "nil must not be transient") + test := func(name string, err error, want bool) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, isTransientReplaceError(err)) + }) + } + test("access denied", errors.New("Access is denied."), true) + test("sharing violation", errors.New("The process cannot access the file because it is being used by another process."), true) + test("missing file", errors.New("no such file or directory"), false) + test("nil error", nil, false) } diff --git a/services/nvpair-cluster-manager/pairing_fail_test.go b/services/nvpair-cluster-manager/pairing_fail_test.go index a3a8d264..1f3818d5 100644 --- a/services/nvpair-cluster-manager/pairing_fail_test.go +++ b/services/nvpair-cluster-manager/pairing_fail_test.go @@ -33,30 +33,20 @@ func putPendingInviterSession(t *testing.T, m *Manager, inviteID string) { // invite, and an empty reason (e.g. a transport-failure fail) still cleans up. // Every case ends the invite in state:"failed". func TestHandlePairingFailed_RejectsInvalidFailureReasons(t *testing.T) { - cases := []struct { - name string - sentReason string - wantReason string - }{ - {"wrong pin is honored", reasonIncorrectPIN, reasonIncorrectPIN}, - {"arbitrary reason is sanitized", "evil-arbitrary-text", ""}, - {"empty reason is preserved", "", ""}, - } - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { + test := func(name, sentReason, wantReason string) { + t.Run(name, func(t *testing.T) { m := newTestManager(t) // clustered as cluster-1, inviteCreated=false m.addSelfMember() putPendingInviterSession(t, m, "inv-fail") m.handlePairingFailed(httptest.NewRecorder(), &pairingEnvelope{ - InviteID: "inv-fail", Phase: "fail", Reason: tc.sentReason, + InviteID: "inv-fail", Phase: "fail", Reason: sentReason, }) inv, ok := m.getInvite("inv-fail") require.True(t, ok, "invite missing after fail signal; want a failed record") require.Equal(t, inviteStateFailed, inv.State, "invite state") - require.Equal(t, tc.wantReason, inv.Reason, "invite reason") + require.Equal(t, wantReason, inv.Reason, "invite reason") // The EAP session is always torn down (PIN invalidated), whatever // the reason. _, ok = m.getSession("inv-fail") @@ -66,6 +56,9 @@ func TestHandlePairingFailed_RejectsInvalidFailureReasons(t *testing.T) { require.NotEqual(t, "", id, "intentional cluster erased by a fail signal") }) } + test("wrong pin is honored", reasonIncorrectPIN, reasonIncorrectPIN) + test("arbitrary reason is sanitized", "evil-arbitrary-text", "") + test("empty reason is preserved", "", "") } // TestHandlePairingFailedIdempotent verifies a duplicate or late fail signal is diff --git a/services/nvpair-cluster-manager/removal_proof_test.go b/services/nvpair-cluster-manager/removal_proof_test.go index 1199c388..ac18840a 100644 --- a/services/nvpair-cluster-manager/removal_proof_test.go +++ b/services/nvpair-cluster-manager/removal_proof_test.go @@ -205,25 +205,21 @@ func TestRemovalProofTampering_FailsClosed(t *testing.T) { require.NoError(t, err) base, _ := relay.removalProofFor(victim.identity.NodeUUID) - cases := map[string]func(*RemovalProof){ - "victim uuid": func(p *RemovalProof) { p.Tombstone.NodeUUID = "other" }, - "cluster": func(p *RemovalProof) { p.Tombstone.ClusterID = "other" }, - "victim admission": func(p *RemovalProof) { p.Tombstone.AdmissionEpoch++ }, - "remover admission": func(p *RemovalProof) { p.Tombstone.ByAdmissionEpoch++ }, - "signer cert": func(p *RemovalProof) { - p.SignerCertPem = string(victim.identity.CertPEM) - }, - "fingerprint": func(p *RemovalProof) { p.SignerFingerprint = "sha256:bad" }, - "tombstone sig": func(p *RemovalProof) { p.Tombstone.SigV2 = "bad" }, - "relay endorsement": func(p *RemovalProof) { p.Endorsements[0].SigV2 = "bad" }, - } - for name, mutate := range cases { + test := func(name string, mutate func(*RemovalProof)) { t.Run(name, func(t *testing.T) { p := cloneProof(t, base) mutate(&p) require.False(t, victim.rejectionProvesRemoval(proofRejectionBody(t, p), relay.identity.NodeUUID), "tampered proof was accepted") }) } + test("victim uuid", func(p *RemovalProof) { p.Tombstone.NodeUUID = "other" }) + test("cluster", func(p *RemovalProof) { p.Tombstone.ClusterID = "other" }) + test("victim admission", func(p *RemovalProof) { p.Tombstone.AdmissionEpoch++ }) + test("remover admission", func(p *RemovalProof) { p.Tombstone.ByAdmissionEpoch++ }) + test("signer cert", func(p *RemovalProof) { p.SignerCertPem = string(victim.identity.CertPEM) }) + test("fingerprint", func(p *RemovalProof) { p.SignerFingerprint = "sha256:bad" }) + test("tombstone sig", func(p *RemovalProof) { p.Tombstone.SigV2 = "bad" }) + test("relay endorsement", func(p *RemovalProof) { p.Endorsements[0].SigV2 = "bad" }) } func TestNewerAdmission_SupersedesProofAndStaleGossip(t *testing.T) { diff --git a/services/nvpair-cluster-manager/self_remove_boundary_test.go b/services/nvpair-cluster-manager/self_remove_boundary_test.go index 75de0c53..dce6e581 100644 --- a/services/nvpair-cluster-manager/self_remove_boundary_test.go +++ b/services/nvpair-cluster-manager/self_remove_boundary_test.go @@ -50,57 +50,8 @@ func pinMemberFixture(t *testing.T, m *Manager, host string) pinFixture { // launches each operation, and asserts it cannot commit while the boundary is // held; then it runs the teardown, releases, and checks the terminal state. func TestSelfRemoveTeardown_SerializesRejoin(t *testing.T) { - cases := []struct { - name string - rejoin func(m *Manager, p2 pinFixture) - assertDone func(t *testing.T, m *Manager, p1, p2 pinFixture) - }{ - { - name: "stale set-identity restore behind teardown is refused", - rejoin: func(m *Manager, _ pinFixture) { - // The broker reflecting its still-persisted clusterId (same id) - // after the node has already been unclustered by the teardown. - params, _ := json.Marshal(map[string]string{"clusterId": "cluster-1", "clusterFriendlyName": "Restored"}) - m.handleSetIdentity(&Message{Params: params}) - }, - assertDone: func(t *testing.T, m *Manager, p1, _ pinFixture) { - id, _ := m.clusterIdentity() - require.Equal(t, "", id, "after teardown a stale set-identity restore left clusterId") - _, ok := m.trust.Get(p1.uuid) - require.False(t, ok, "a pin survived teardown+stale-restore; want an empty pin set") - require.Empty(t, m.snapshotNodes()) - }, - }, - { - name: "full pairing commit stays atomic across teardown", - rejoin: func(m *Manager, p2 pinFixture) { - m.withClusterComposition(func() { - m.setClusterIdentity("cluster-2", "Rejoined") - _ = m.trust.Pin(&TrustedPin{ - NodeUUID: p2.uuid, NodeID: "peer-2", ClusterID: "cluster-2", - CertPem: p2.cert, CertFingerprint: p2.fp, PinnedAt: time.Now().UnixMilli(), - }) - m.upsertMember(&ClusterNode{NodeUUID: p2.uuid, ID: "peer-2", ClusterID: "cluster-2", State: stateMember}) - m.addSelfMember() - }) - }, - assertDone: func(t *testing.T, m *Manager, p1, p2 pinFixture) { - id, _ := m.clusterIdentity() - require.Equal(t, "cluster-2", id, "after rejoin clusterId") - _, ok := m.trust.Get(p2.uuid) - require.True(t, ok, "rejoined peer pin missing after teardown+rejoin") - _, ok = m.trust.Get(p1.uuid) - require.False(t, ok, "old cluster-1 pin survived teardown; state is inconsistent") - for _, n := range m.snapshotNodes() { - require.NotEqual(t, p1.uuid, n.NodeUUID, "old cluster-1 member survived teardown; state is inconsistent") - } - }, - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { + test := func(name string, rejoin func(*testing.T, *Manager, pinFixture), assertDone func(*testing.T, *Manager, pinFixture, pinFixture)) { + t.Run(name, func(t *testing.T) { m := newTestManagerPort(t, 15031) p1 := pinMemberFixture(t, m, "peer-1") // pinned member in cluster-1 p2 := newPinFixture(t, "peer-2") // identity to rejoin with @@ -111,7 +62,7 @@ func TestSelfRemoveTeardown_SerializesRejoin(t *testing.T) { done := make(chan struct{}) go func() { - tc.rejoin(m, p2) + rejoin(t, m, p2) close(done) }() @@ -138,7 +89,40 @@ func TestSelfRemoveTeardown_SerializesRejoin(t *testing.T) { case <-time.After(5 * time.Second): require.FailNow(t, "rejoin did not complete after the boundary was released") } - tc.assertDone(t, m, p1, p2) + assertDone(t, m, p1, p2) }) } + test("stale set-identity restore behind teardown is refused", func(t *testing.T, m *Manager, _ pinFixture) { + // The broker reflecting its still-persisted clusterId (same id) + // after the node has already been unclustered by the teardown. + params, _ := json.Marshal(map[string]string{"clusterId": "cluster-1", "clusterFriendlyName": "Restored"}) + m.handleSetIdentity(&Message{Params: params}) + }, func(t *testing.T, m *Manager, p1, _ pinFixture) { + id, _ := m.clusterIdentity() + require.Equal(t, "", id, "after teardown a stale set-identity restore left clusterId") + _, ok := m.trust.Get(p1.uuid) + require.False(t, ok, "a pin survived teardown+stale-restore; want an empty pin set") + require.Empty(t, m.snapshotNodes()) + }) + test("full pairing commit stays atomic across teardown", func(t *testing.T, m *Manager, p2 pinFixture) { + m.withClusterComposition(func() { + m.setClusterIdentity("cluster-2", "Rejoined") + _ = m.trust.Pin(&TrustedPin{ + NodeUUID: p2.uuid, NodeID: "peer-2", ClusterID: "cluster-2", + CertPem: p2.cert, CertFingerprint: p2.fp, PinnedAt: time.Now().UnixMilli(), + }) + m.upsertMember(&ClusterNode{NodeUUID: p2.uuid, ID: "peer-2", ClusterID: "cluster-2", State: stateMember}) + m.addSelfMember() + }) + }, func(t *testing.T, m *Manager, p1, p2 pinFixture) { + id, _ := m.clusterIdentity() + require.Equal(t, "cluster-2", id, "after rejoin clusterId") + _, ok := m.trust.Get(p2.uuid) + require.True(t, ok, "rejoined peer pin missing after teardown+rejoin") + _, ok = m.trust.Get(p1.uuid) + require.False(t, ok, "old cluster-1 pin survived teardown; state is inconsistent") + for _, n := range m.snapshotNodes() { + require.NotEqual(t, p1.uuid, n.NodeUUID, "old cluster-1 member survived teardown; state is inconsistent") + } + }) } diff --git a/services/nvpair-cluster-manager/self_remove_test.go b/services/nvpair-cluster-manager/self_remove_test.go index ddfb41cd..2dd30269 100644 --- a/services/nvpair-cluster-manager/self_remove_test.go +++ b/services/nvpair-cluster-manager/self_remove_test.go @@ -49,39 +49,10 @@ func selfRemovalProof(peer, victim *Manager, cluster string) []byte { // returning 403, so the state change lands deterministically inside the wait // window rather than relying on timing. func TestSelfRemoval_IgnoresRejectionFromPreviousMembership(t *testing.T) { - cases := []struct { - name string - // mutate runs while the peer reconcile is blocked, i.e. after the verdict's - // inputs are snapshotted but before it decides. nil means no change. - mutate func(m *Manager) - // wantCluster is the clusterId expected after the pass ("" = torn down). - wantCluster string - }{ - { - name: "no change self-removes", - mutate: nil, - wantCluster: "", - }, - { - name: "rejoin into a different cluster is preserved", - mutate: func(m *Manager) { m.setClusterIdentity("cluster-2", "New Cluster") }, - wantCluster: "cluster-2", - }, - { - name: "re-pin back into the same cluster is preserved", - mutate: func(m *Manager) { - // A re-pair back into the same cluster keeps clusterId but bumps - // the generation (fresh pin + member record) — precisely the case - // a clusterId-only revalidation would miss. - m.upsertMember(&ClusterNode{NodeUUID: "rejoined-peer", ID: "rejoined-peer", State: stateMember}) - }, - wantCluster: "cluster-1", - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { + // mutate runs after the verdict's inputs are snapshotted, while reconcile + // is blocked. A nil mutation leaves the original cluster unchanged. + test := func(name string, mutate func(*Manager), wantCluster string) { + t.Run(name, func(t *testing.T) { mA := newTestManagerPort(t, 15021) // peer contributes only its (pinned) identity/cert; its own server is // never started — the blocking stub below stands in for it. @@ -134,8 +105,8 @@ func TestSelfRemoval_IgnoresRejectionFromPreviousMembership(t *testing.T) { case <-time.After(10 * time.Second): require.FailNow(t, "peer reconcile never reached the handler") } - if tc.mutate != nil { - tc.mutate(mA) + if mutate != nil { + mutate(mA) } close(release) @@ -146,9 +117,18 @@ func TestSelfRemoval_IgnoresRejectionFromPreviousMembership(t *testing.T) { } got, _ := mA.clusterIdentity() - require.Equal(t, tc.wantCluster, got, "after reconcile pass clusterId") + require.Equal(t, wantCluster, got, "after reconcile pass clusterId") }) } + test("no change self-removes", nil, "") + test("rejoin into a different cluster is preserved", func(m *Manager) { + m.setClusterIdentity("cluster-2", "New Cluster") + }, "cluster-2") + test("re-pin back into the same cluster is preserved", func(m *Manager) { + // A re-pair keeps clusterId but bumps the generation (fresh pin + + // member record), which a clusterId-only revalidation would miss. + m.upsertMember(&ClusterNode{NodeUUID: "rejoined-peer", ID: "rejoined-peer", State: stateMember}) + }, "cluster-1") } // startPeerStub mints a peer identity, pins it into m as a cluster-1 member, and @@ -213,37 +193,21 @@ func startPeerStub(t *testing.T, m *Manager, id string, status int, proof bool) // teardown. (A unanimous 403 with *no* proof is covered separately — it must // NOT self-remove.) func TestSelfRemove_RequiresUnanimousRejection(t *testing.T) { - cases := []struct { - name string - peerStatus []int - wantCluster string // "" = torn down - }{ - { - name: "mixed 200 and 403 stays clustered", - peerStatus: []int{http.StatusOK, http.StatusForbidden}, - wantCluster: "cluster-1", - }, - { - name: "unanimous 403 across multiple peers self-removes", - peerStatus: []int{http.StatusForbidden, http.StatusForbidden}, - wantCluster: "", - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { + test := func(name string, peerStatus []int, wantCluster string) { + t.Run(name, func(t *testing.T) { mA := newTestManagerPort(t, 15061) // clustered as cluster-1 - for i, st := range tc.peerStatus { + for i, st := range peerStatus { startPeerStub(t, mA, fmt.Sprintf("peer-%d", i), st, st == http.StatusForbidden) } mA.reconcilePeersAndMaybeSelfRemove() got, _ := mA.clusterIdentity() - require.Equal(t, tc.wantCluster, got, "after reconcile clusterId") + require.Equal(t, wantCluster, got, "after reconcile clusterId") }) } + test("mixed 200 and 403 stays clustered", []int{http.StatusOK, http.StatusForbidden}, "cluster-1") + test("unanimous 403 across multiple peers self-removes", []int{http.StatusForbidden, http.StatusForbidden}, "") } // TestSelfRemoveRequiresRemovalProof is the regression for the blocker: a @@ -262,45 +226,21 @@ func TestSelfRemove_RequiresRemovalProof(t *testing.T) { proof bool } forbidden := http.StatusForbidden - cases := []struct { - name string - peers []peerSpec - wantCluster string // "" = torn down - }{ - { - name: "only peer left (bare 403, no proof) — survivor stays", - peers: []peerSpec{{forbidden, false}}, - wantCluster: "cluster-1", - }, - { - name: "only peer removed us (403 + signed tombstone) — self-removes", - peers: []peerSpec{{forbidden, true}}, - wantCluster: "", - }, - { - name: "every peer left (unanimous bare 403) — survivor stays", - peers: []peerSpec{{forbidden, false}, {forbidden, false}}, - wantCluster: "cluster-1", - }, - { - name: "removed by one peer while another left — self-removes", - peers: []peerSpec{{forbidden, true}, {forbidden, false}}, - wantCluster: "", - }, - } - - for _, tc := range cases { - tc := tc - t.Run(tc.name, func(t *testing.T) { + test := func(name string, peers []peerSpec, wantCluster string) { + t.Run(name, func(t *testing.T) { mA := newTestManagerPort(t, 15071) // clustered as cluster-1 - for i, ps := range tc.peers { + for i, ps := range peers { startPeerStub(t, mA, fmt.Sprintf("peer-%d", i), ps.status, ps.proof) } mA.reconcilePeersAndMaybeSelfRemove() got, _ := mA.clusterIdentity() - require.Equal(t, tc.wantCluster, got, "after reconcile clusterId") + require.Equal(t, wantCluster, got, "after reconcile clusterId") }) } + test("only peer left (bare 403, no proof) — survivor stays", []peerSpec{{forbidden, false}}, "cluster-1") + test("only peer removed us (403 + signed tombstone) — self-removes", []peerSpec{{forbidden, true}}, "") + test("every peer left (unanimous bare 403) — survivor stays", []peerSpec{{forbidden, false}, {forbidden, false}}, "cluster-1") + test("removed by one peer while another left — self-removes", []peerSpec{{forbidden, true}, {forbidden, false}}, "") } diff --git a/services/nvpair-engine-manager/catalog_test.go b/services/nvpair-engine-manager/catalog_test.go index 19f96d43..73d14d65 100644 --- a/services/nvpair-engine-manager/catalog_test.go +++ b/services/nvpair-engine-manager/catalog_test.go @@ -198,20 +198,21 @@ func TestLmStudioCatalog_BacksOffWithNothingCached(t *testing.T) { } func TestNormalizeCatalogEngine_ResolvesSupportedEngineNames(t *testing.T) { - expectNormalized := func(in, want string) { - t.Helper() - assert.Equal(t, want, normalizeCatalogEngine(in), "normalizeCatalogEngine(%q)", in) + test := func(name, input, want string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, normalizeCatalogEngine(input)) + }) } - expectNormalized("ollama", "ollama") - expectNormalized("Ollama", "ollama") - expectNormalized(" ollama ", "ollama") - expectNormalized("lmstudio", "lmstudio") - expectNormalized("LM Studio", "lmstudio") - expectNormalized("lm-studio", "lmstudio") - expectNormalized("llamacpp", "llamacpp") - expectNormalized("llama-cpp", "llamacpp") - expectNormalized("llama.cpp", "llamacpp") - expectNormalized("vllm", "vllm") + test("canonical Ollama", "ollama", "ollama") + test("case insensitive Ollama", "Ollama", "ollama") + test("trimmed Ollama", " ollama ", "ollama") + test("canonical LM Studio", "lmstudio", "lmstudio") + test("spaced LM Studio", "LM Studio", "lmstudio") + test("hyphenated LM Studio", "lm-studio", "lmstudio") + test("canonical llama.cpp", "llamacpp", "llamacpp") + test("hyphenated llama.cpp", "llama-cpp", "llamacpp") + test("dotted llama.cpp", "llama.cpp", "llamacpp") + test("other engine preserved", "vllm", "vllm") } // TestCatalogRejectsUnknownEngine checks an engine with no curated source errors @@ -255,17 +256,18 @@ func TestFilterForTarget_RestrictsMLXModelsToAppleSilicon(t *testing.T) { } assert.Len(t, filterForTarget(models, "darwin", "arm64"), 2, "Apple Silicon target must keep both models") - for _, target := range []struct{ platform, arch string }{ - {"darwin", "amd64"}, - {"darwin", ""}, - {"linux", "arm64"}, - {"windows", "amd64"}, - } { - got := filterForTarget(models, target.platform, target.arch) - if assert.Len(t, got, 1, "%s/%s target must keep only the portable model", target.platform, target.arch) { - assert.Equal(t, "plain/gguf", got[0].ID, "%s/%s target", target.platform, target.arch) - } + test := func(name, platform, arch string) { + t.Run(name, func(t *testing.T) { + got := filterForTarget(models, platform, arch) + if assert.Len(t, got, 1, "target must keep only the portable model") { + assert.Equal(t, "plain/gguf", got[0].ID) + } + }) } + test("Intel Mac", "darwin", "amd64") + test("Mac with unknown architecture", "darwin", "") + test("ARM Linux", "linux", "arm64") + test("Windows", "windows", "amd64") } // TestCatalogEchoesTarget checks the reply says which machine it was filtered diff --git a/services/nvpair-engine-manager/executor_test.go b/services/nvpair-engine-manager/executor_test.go index 4500075a..4031f37f 100644 --- a/services/nvpair-engine-manager/executor_test.go +++ b/services/nvpair-engine-manager/executor_test.go @@ -295,15 +295,15 @@ func TestStart_UsesRequestedPort(t *testing.T) { } func TestEffectiveBind_PrefersExplicitOverrideOverManifest(t *testing.T) { - cases := []struct{ manifest, override, want string }{ - {"", "", "127.0.0.1"}, // ordinary engine: safe default - {"0.0.0.0", "", "0.0.0.0"}, // inference engine declares open - {"0.0.0.0", "127.0.0.1", "127.0.0.1"}, // per-call lock-down wins - {"", "192.168.1.5", "192.168.1.5"}, // per-call specific interface - } - for _, c := range cases { - assert.Equal(t, c.want, effectiveBind(c.manifest, c.override), "effectiveBind") + test := func(name, manifest, override, want string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, effectiveBind(manifest, override)) + }) } + test("safe default", "", "", "127.0.0.1") + test("manifest declares open", "0.0.0.0", "", "0.0.0.0") + test("per-call lock-down wins", "0.0.0.0", "127.0.0.1", "127.0.0.1") + test("per-call specific interface", "", "192.168.1.5", "192.168.1.5") } // TestStatusReportsExternallyRunning covers the adoption false-negative: diff --git a/services/nvpair-engine-manager/launch_controls_test.go b/services/nvpair-engine-manager/launch_controls_test.go index f901ac1c..d789be56 100644 --- a/services/nvpair-engine-manager/launch_controls_test.go +++ b/services/nvpair-engine-manager/launch_controls_test.go @@ -19,11 +19,8 @@ import ( ) func TestLaunchControls_BuildEquivalentLaunchesAcrossSources(t *testing.T) { - for _, definition := range []string{ - `[{"value":"{server.port}","env":["SERVER_PORT","PORT_ALIAS"]},{"value":"{server.host}","env":["SERVER_HOST","HOST_ALIAS"]},{"value":"{cors.enabled}","flags":["--browser-access","-c"],"env":["BROWSER_ACCESS","CORS_ALIAS"]}]`, - `[{"value":"{server.host}:{server.port}","flags":["--listen","-l"],"env":["LISTEN","LISTEN_ALIAS"]},{"value":"{cors.origins}","flags":["--origins","-o"],"env":["ORIGINS","ORIGIN_ALIAS"]}]`, - } { - t.Run(definition, func(t *testing.T) { + test := func(name, definition string) { + t.Run(name, func(t *testing.T) { e := settingsExecutor(t, true) rt := &settingsState(t, e).plat.Runtime require.NoError(t, json.Unmarshal([]byte(definition), &rt.EditableLaunch.Controls)) @@ -57,36 +54,42 @@ func TestLaunchControls_BuildEquivalentLaunchesAcrossSources(t *testing.T) { require.NotEmpty(t, previewSettings(t, e, request).Errors, "contradictory source aliases accepted") }) } + test("separate host and port", `[{"value":"{server.port}","env":["SERVER_PORT","PORT_ALIAS"]},{"value":"{server.host}","env":["SERVER_HOST","HOST_ALIAS"]},{"value":"{cors.enabled}","flags":["--browser-access","-c"],"env":["BROWSER_ACCESS","CORS_ALIAS"]}]`) + test("combined listen address", `[{"value":"{server.host}:{server.port}","flags":["--listen","-l"],"env":["LISTEN","LISTEN_ALIAS"]},{"value":"{cors.origins}","flags":["--origins","-o"],"env":["ORIGINS","ORIGIN_ALIAS"]}]`) } func TestSavedControls_CannotBypassLaunchValidation(t *testing.T) { - for _, command := range []bool{false, true} { - e := settingsExecutor(t, command) - rt := settingsState(t, e).plat.Runtime - request := settingsRequest(t, e) - launch, err := launchForState(settingsState(t, e), request.Settings.ServerPort) - require.NoError(t, err) - if command { - launch.Args = append(launch.Args, "-p0") - } else { - launch.Env["OLLAMA_ORIGINS"] = `"*"` - } - require.Error(t, validateEffectiveLaunch(rt, launch, "127.0.0.1", fmt.Sprint(request.Settings.ServerPort)), "unsafe saved launch accepted") - if command { - args := []string{"-p0"} - rt.LaunchArgs = args - } else { - env := []string{`OLLAMA_ORIGINS="*"`} - rt.LaunchEnv = env - } - settingsState(t, e).plat.Runtime = rt - state, err := e.LaunchSettings("fake") - require.NoError(t, err, "saved settings must remain available for repair (%v, %v)", state, err) - require.True(t, state.Editable, "saved settings must remain available for repair (%v, %v)", state, err) - err = e.Start(context.Background(), "fake") - require.Error(t, err, "Start did not reject the unsafe saved networking control") - require.True(t, strings.Contains(err.Error(), "server port") || strings.Contains(err.Error(), "CORS origins"), "Start did not reject the unsafe saved networking control (%v)", err) + test := func(name string, command bool) { + t.Run(name, func(t *testing.T) { + e := settingsExecutor(t, command) + rt := settingsState(t, e).plat.Runtime + request := settingsRequest(t, e) + launch, err := launchForState(settingsState(t, e), request.Settings.ServerPort) + require.NoError(t, err) + if command { + launch.Args = append(launch.Args, "-p0") + } else { + launch.Env["OLLAMA_ORIGINS"] = `"*"` + } + require.Error(t, validateEffectiveLaunch(rt, launch, "127.0.0.1", fmt.Sprint(request.Settings.ServerPort)), "unsafe saved launch accepted") + if command { + args := []string{"-p0"} + rt.LaunchArgs = args + } else { + env := []string{`OLLAMA_ORIGINS="*"`} + rt.LaunchEnv = env + } + settingsState(t, e).plat.Runtime = rt + state, err := e.LaunchSettings("fake") + require.NoError(t, err, "saved settings must remain available for repair") + require.True(t, state.Editable, "saved settings must remain available for repair") + err = e.Start(context.Background(), "fake") + require.Error(t, err, "Start did not reject the unsafe saved networking control") + require.True(t, strings.Contains(err.Error(), "server port") || strings.Contains(err.Error(), "CORS origins"), "Start did not reject the unsafe saved networking control (%v)", err) + }) } + test("process", false) + test("command", true) } func TestBundledLlamaCPP_DisablesCORSByDefault(t *testing.T) { @@ -172,20 +175,30 @@ func TestNetworkingControlAliases_AreEngineIndependent(t *testing.T) { {Value: "{cors.enabled}", Implicit: implicitLaunchValue("true"), Flags: []string{"--browser-access", "--allow-browser", "-c"}}, } - for _, text := range []string{"--http-port=23456 -b127.0.0.1 -ohttps://example.test", "-x23456 --address=127.0.0.1 --browser-origins=https://example.test"} { - request := settingsRequest(t, e) - request.Settings.LaunchText = text - request.Resolution = "launch" - result := previewSettings(t, e, request) - require.Empty(t, result.Errors, " (%v, %v)", text, result) - require.Equal(t, 23456, result.Settings.ServerPort, " (%v, %v)", text, result) - assert.Equal(t, []string{"--origins", "https://example.test"}, result.Args, " (%v)", text) - } - for _, text := range []string{"--address=0.0.0.0", "-b0.0.0.0", "--browser-origins=*", "-o*", "-vc", "-vohttps://example.test", "-ohttps://one.test --origins=https://two.test"} { - request := settingsRequest(t, e) - request.Settings.LaunchText = text - require.NotEmpty(t, previewSettings(t, e, request).Errors, "accepted (%v)", text) + test := func(name, text string, valid bool) { + t.Run(name, func(t *testing.T) { + request := settingsRequest(t, e) + request.Settings.LaunchText = text + if !valid { + require.NotEmpty(t, previewSettings(t, e, request).Errors, "unsafe networking aliases accepted") + return + } + request.Resolution = "launch" + result := previewSettings(t, e, request) + require.Empty(t, result.Errors) + require.Equal(t, 23456, result.Settings.ServerPort) + assert.Equal(t, []string{"--origins", "https://example.test"}, result.Args) + }) } + test("long port and short host and origins", "--http-port=23456 -b127.0.0.1 -ohttps://example.test", true) + test("short port and long host and origins", "-x23456 --address=127.0.0.1 --browser-origins=https://example.test", true) + test("long host binds externally", "--address=0.0.0.0", false) + test("short host binds externally", "-b0.0.0.0", false) + test("long origins accepts all", "--browser-origins=*", false) + test("short origins accepts all", "-o*", false) + test("grouped CORS flag", "-vc", false) + test("grouped origins flag", "-vohttps://example.test", false) + test("conflicting origins aliases", "-ohttps://one.test --origins=https://two.test", false) } func TestCORSOrigins_NormalizesEquivalentPolicies(t *testing.T) { @@ -193,22 +206,23 @@ func TestCORSOrigins_NormalizesEquivalentPolicies(t *testing.T) { _, err := normalizeCORSOrigins(text) assert.Error(t, err, "accepted (%v)", text) } - for _, value := range []struct{ input, want string }{ - {`"https://Example.Test"`, "https://example.test"}, - {" https://example.test/ , http://localhost:* ", "https://example.test,http://localhost:*"}, - {"https://*.example.test", "https://*.example.test"}, - {"http://[::1]:1234", "http://[::1]:1234"}, - {"", ""}, - } { - got, err := normalizeCORSOrigins(value.input) - assert.NoError(t, err, " (%v, %v)", got, err) - assert.Equal(t, value.want, got) + test := func(name, input, want string) { + t.Run(name, func(t *testing.T) { + got, err := normalizeCORSOrigins(input) + assert.NoError(t, err) + assert.Equal(t, want, got) + }) } + test("case and quotes", `"https://Example.Test"`, "https://example.test") + test("whitespace and trailing slash", " https://example.test/ , http://localhost:* ", "https://example.test,http://localhost:*") + test("wildcard subdomain", "https://*.example.test", "https://*.example.test") + test("IPv6 origin", "http://[::1]:1234", "http://[::1]:1234") + test("empty origins", "", "") } func TestRemoteCORS_UsesCanonicalPolicyAndAuthoritativePreview(t *testing.T) { - for _, command := range []bool{false, true} { - t.Run(fmt.Sprint(command), func(t *testing.T) { + test := func(name string, command bool) { + t.Run(name, func(t *testing.T) { e := settingsExecutor(t, command) request := settingsRequest(t, e) changed := "OLLAMA_ORIGINS=https://example.test" @@ -236,11 +250,13 @@ func TestRemoteCORS_UsesCanonicalPolicyAndAuthoritativePreview(t *testing.T) { require.Error(t, err, "remote removal changed policy") }) } + test("process", false) + test("command", true) } // Removing a disabling override may restore permissive inherited/default values. func TestRemoteCORS_CannotRemoveExplicitDisablingValues(t *testing.T) { - for _, kind := range []string{"cors.enabled", "cors.origins"} { + test := func(kind string) { t.Run(kind, func(t *testing.T) { e := settingsExecutor(t, true) policy := settingsState(t, e).plat.Runtime.EditableLaunch @@ -261,6 +277,8 @@ func TestRemoteCORS_CannotRemoveExplicitDisablingValues(t *testing.T) { require.Error(t, err, "remote removal of disabling override accepted") }) } + test("cors.enabled") + test("cors.origins") } func TestNetworkingManifest_RejectsAmbiguousDeclarations(t *testing.T) { diff --git a/services/nvpair-engine-manager/launch_test.go b/services/nvpair-engine-manager/launch_test.go index fa3bd386..7c1a3b56 100644 --- a/services/nvpair-engine-manager/launch_test.go +++ b/services/nvpair-engine-manager/launch_test.go @@ -193,10 +193,14 @@ func TestSavedLaunchArguments_ReplaceManifestDefaults(t *testing.T) { } func TestCommandLaunch_RejectsEmptyExecutable(t *testing.T) { - for _, template := range [][]string{{"", "start"}, {"{cli}", "start"}} { - _, err := resolveCommandLaunch(template, map[string]string{"cli": ""}) - require.Error(t, err, "empty executable must fail rather than skip the start command") + test := func(name string, template []string) { + t.Run(name, func(t *testing.T) { + _, err := resolveCommandLaunch(template, map[string]string{"cli": ""}) + require.Error(t, err, "empty executable must fail rather than skip the start command") + }) } + test("literal empty executable", []string{"", "start"}) + test("expanded empty executable", []string{"{cli}", "start"}) } // The child receives the parser's output directly. This catches escaping @@ -221,7 +225,7 @@ func TestLaunchText_ReachesChildLiterally(t *testing.T) { } func TestLifecycle_UsesResolvedLaunchBuilder(t *testing.T) { - for _, mode := range []string{"process", "command"} { + test := func(mode string) { t.Run(mode, func(t *testing.T) { path := filepath.Join(t.TempDir(), "captured args.json") manifest := testEngineManifest(fakeEngineBin) @@ -252,6 +256,8 @@ func TestLifecycle_UsesResolvedLaunchBuilder(t *testing.T) { assertCapturedArgs(t, path, []string{"127.0.0.1", "25001", "two words", ""}) }) } + test("process") + test("command") } func assertCapturedArgs(t *testing.T, path string, want []string) { diff --git a/services/nvpair-engine-manager/lifecycle_stop_test.go b/services/nvpair-engine-manager/lifecycle_stop_test.go index e5946129..521b3ba7 100644 --- a/services/nvpair-engine-manager/lifecycle_stop_test.go +++ b/services/nvpair-engine-manager/lifecycle_stop_test.go @@ -21,21 +21,15 @@ import ( ) func TestStop_TerminatesManagedEngineWithinGracePeriod(t *testing.T) { - tests := []struct { - name string - stop *StopSpec - want time.Duration - }{ - {name: "default without stop spec", want: 5 * time.Second}, - {name: "default with zero grace", stop: &StopSpec{Signal: "term"}, want: 5 * time.Second}, - {name: "configured grace", stop: &StopSpec{Signal: "term", GraceS: 10}, want: 10 * time.Second}, - {name: "kill is immediate", stop: &StopSpec{Signal: "kill", GraceS: 10}, want: 0}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.want, stopGrace(Runtime{Stop: tt.stop})) + test := func(name string, stop *StopSpec, want time.Duration) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, stopGrace(Runtime{Stop: stop})) }) } + test("default without stop spec", nil, 5*time.Second) + test("default with zero grace", &StopSpec{Signal: "term"}, 5*time.Second) + test("configured grace", &StopSpec{Signal: "term", GraceS: 10}, 10*time.Second) + test("kill is immediate", &StopSpec{Signal: "kill", GraceS: 10}, 0) } // spawnFakeListener starts a fake-engine copied to binPath, bound to diff --git a/services/nvpair-engine-manager/lifecycle_stop_unix_test.go b/services/nvpair-engine-manager/lifecycle_stop_unix_test.go index 6cc72d71..12545abd 100644 --- a/services/nvpair-engine-manager/lifecycle_stop_unix_test.go +++ b/services/nvpair-engine-manager/lifecycle_stop_unix_test.go @@ -46,50 +46,46 @@ while :; do sleep 1; done` } func TestStop_HonorsProcessSignalPolicy(t *testing.T) { - test := func(t *testing.T, stop StopSpec, minElapsed, maxElapsed time.Duration) { - t.Helper() - proc := startTermIgnoringProcess(t) - manifest := testEngineManifest(fakeEngineBin) - key := runtime.GOOS + "/" + runtime.GOARCH - platform := manifest.Platforms[key] - platform.Runtime.Stop = &stop - manifest.Platforms[key] = platform - ex := newTestExecutor(t, manifest) - state, err := ex.state(manifest.Engine) - require.NoError(t, err, "resolve engine state") - state.mu.Lock() - state.running = true - state.healthy = true - state.proc = proc - state.mu.Unlock() + test := func(name string, stop StopSpec, minElapsed, maxElapsed time.Duration) { + t.Run(name, func(t *testing.T) { + proc := startTermIgnoringProcess(t) + manifest := testEngineManifest(fakeEngineBin) + key := runtime.GOOS + "/" + runtime.GOARCH + platform := manifest.Platforms[key] + platform.Runtime.Stop = &stop + manifest.Platforms[key] = platform + ex := newTestExecutor(t, manifest) + state, err := ex.state(manifest.Engine) + require.NoError(t, err, "resolve engine state") + state.mu.Lock() + state.running = true + state.healthy = true + state.proc = proc + state.mu.Unlock() - started := time.Now() - result := make(chan error, 1) - go func() { - result <- ex.Stop(manifest.Engine) - }() - select { - case err := <-result: - require.NoError(t, err, "stop engine") - case <-time.After(maxElapsed + time.Second): - _ = signalPID(proc.cmd.Process.Pid, true) - require.FailNowf(t, "stop did not finish", "within %s", maxElapsed+time.Second) - } + started := time.Now() + result := make(chan error, 1) + go func() { + result <- ex.Stop(manifest.Engine) + }() + select { + case err := <-result: + require.NoError(t, err, "stop engine") + case <-time.After(maxElapsed + time.Second): + _ = signalPID(proc.cmd.Process.Pid, true) + require.FailNowf(t, "stop did not finish", "within %s", maxElapsed+time.Second) + } - elapsed := time.Since(started) - require.GreaterOrEqual(t, elapsed, minElapsed) - require.LessOrEqual(t, elapsed, maxElapsed) - select { - case <-proc.done: - default: - require.FailNow(t, "stop returned before the owned process exited") - } + elapsed := time.Since(started) + require.GreaterOrEqual(t, elapsed, minElapsed) + require.LessOrEqual(t, elapsed, maxElapsed) + select { + case <-proc.done: + default: + require.FailNow(t, "stop returned before the owned process exited") + } + }) } - - t.Run("term escalates after configured grace", func(t *testing.T) { - test(t, StopSpec{Signal: "term", GraceS: 1}, 800*time.Millisecond, 3*time.Second) - }) - t.Run("kill bypasses configured grace", func(t *testing.T) { - test(t, StopSpec{Signal: "kill", GraceS: 10}, 0, 2*time.Second) - }) + test("term escalates after configured grace", StopSpec{Signal: "term", GraceS: 1}, 800*time.Millisecond, 3*time.Second) + test("kill bypasses configured grace", StopSpec{Signal: "kill", GraceS: 10}, 0, 2*time.Second) } diff --git a/services/nvpair-engine-manager/lmsdelete_test.go b/services/nvpair-engine-manager/lmsdelete_test.go index e54b1bce..82521875 100644 --- a/services/nvpair-engine-manager/lmsdelete_test.go +++ b/services/nvpair-engine-manager/lmsdelete_test.go @@ -17,19 +17,16 @@ func TestLMSEntry_MatchesModel(t *testing.T) { Path: "nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.Q4_K_M.gguf", IndexedModelIdentifier: "nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.Q4_K_M.gguf", } - cases := []struct { - model string - want bool - }{ - {"text-embedding-nomic-embed-text-v1.5", true}, - {"nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.Q4_K_M.gguf", true}, - {"nomic-ai/nomic-embed-text-v1.5-GGUF", true}, - {"publisher/demo-model", false}, - {"", false}, - } - for _, c := range cases { - assert.Equal(t, c.want, lmsEntryMatchesModel(entry, c.model), "lmsEntryMatchesModel") + test := func(name, model string, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, lmsEntryMatchesModel(entry, model)) + }) } + test("model key", "text-embedding-nomic-embed-text-v1.5", true) + test("full path", "nomic-ai/nomic-embed-text-v1.5-GGUF/nomic-embed-text-v1.5.Q4_K_M.gguf", true) + test("repository", "nomic-ai/nomic-embed-text-v1.5-GGUF", true) + test("other model", "publisher/demo-model", false) + test("empty model", "", false) } func TestSafeRemoveUnderRoot_RejectsMissingPath(t *testing.T) { diff --git a/services/nvpair-engine-manager/lmsget_test.go b/services/nvpair-engine-manager/lmsget_test.go index b2aa5ceb..037d50f8 100644 --- a/services/nvpair-engine-manager/lmsget_test.go +++ b/services/nvpair-engine-manager/lmsget_test.go @@ -20,97 +20,61 @@ import ( ) func TestLMSGetCandidates_OrdersModelResolutionFallbacks(t *testing.T) { - cases := []struct { - name string - in string - want []string - }{ - { - name: "bare owner/name falls back to Hugging Face", - in: "lmstudio-community/Qwen3.5-397B-A17B-MLX-4bit", - want: []string{ - "lmstudio-community/Qwen3.5-397B-A17B-MLX-4bit", - "https://huggingface.co/lmstudio-community/Qwen3.5-397B-A17B-MLX-4bit", - }, - }, - { - name: "quant qualifier is preserved", - in: "owner/name@q4_k_m", - want: []string{ - "owner/name@q4_k_m", - "https://huggingface.co/owner/name@q4_k_m", - }, - }, - { - name: "explicit Hugging Face URL is honored first, then Hub", - in: "https://huggingface.co/lmstudio-community/Foo-MLX-4bit", - want: []string{ - "https://huggingface.co/lmstudio-community/Foo-MLX-4bit", - "lmstudio-community/Foo-MLX-4bit", - }, - }, - { - name: "lmstudio.ai /models URL keeps Hub first, then Hugging Face", - in: "https://lmstudio.ai/models/qwen/qwen3.5-9b", - want: []string{ - "https://lmstudio.ai/models/qwen/qwen3.5-9b", - "https://huggingface.co/qwen/qwen3.5-9b", - }, - }, - { - name: "lmstudio.ai URL without /models", - in: "https://lmstudio.ai/qwen/qwen3.5-9b", - want: []string{ - "https://lmstudio.ai/qwen/qwen3.5-9b", - "https://huggingface.co/qwen/qwen3.5-9b", - }, - }, - { - name: "search term passes through unchanged", - in: "llama3.2", - want: []string{"llama3.2"}, - }, - { - name: "unrecognized URL is honored verbatim with no fallback", - in: "https://example.com/a/b", - want: []string{"https://example.com/a/b"}, - }, - { - name: "blank yields no candidates", - in: " ", - want: nil, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, lmsGetCandidates(c.in), "lmsGetCandidates") + test := func(name, input string, want []string) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, lmsGetCandidates(input)) }) } + test("bare owner/name falls back to Hugging Face", "lmstudio-community/Qwen3.5-397B-A17B-MLX-4bit", []string{ + "lmstudio-community/Qwen3.5-397B-A17B-MLX-4bit", + "https://huggingface.co/lmstudio-community/Qwen3.5-397B-A17B-MLX-4bit", + }) + test("quant qualifier is preserved", "owner/name@q4_k_m", []string{"owner/name@q4_k_m", "https://huggingface.co/owner/name@q4_k_m"}) + test("explicit Hugging Face URL is honored first, then Hub", "https://huggingface.co/lmstudio-community/Foo-MLX-4bit", []string{ + "https://huggingface.co/lmstudio-community/Foo-MLX-4bit", + "lmstudio-community/Foo-MLX-4bit", + }) + test("lmstudio.ai /models URL keeps Hub first, then Hugging Face", "https://lmstudio.ai/models/qwen/qwen3.5-9b", []string{ + "https://lmstudio.ai/models/qwen/qwen3.5-9b", + "https://huggingface.co/qwen/qwen3.5-9b", + }) + test("lmstudio.ai URL without /models", "https://lmstudio.ai/qwen/qwen3.5-9b", []string{"https://lmstudio.ai/qwen/qwen3.5-9b", "https://huggingface.co/qwen/qwen3.5-9b"}) + test("search term passes through unchanged", "llama3.2", []string{"llama3.2"}) + test("unrecognized URL is honored verbatim with no fallback", "https://example.com/a/b", []string{"https://example.com/a/b"}) + test("blank yields no candidates", " ", nil) } func TestIsLMSResolveFailure_RecognizesModelResolutionErrors(t *testing.T) { const missingArtifact = `exit status 1: Error: Failed to resolve artifact "x/y": The artifact does not exist or you do not have permission to read it` - assert.True(t, isLMSResolveFailure(errors.New(missingArtifact)), "expected resolve failure") - assert.True(t, isLMSResolveFailure(errors.New("this model is not supported in LM Studio")), "expected resolve failure") - assert.True(t, isLMSResolveFailure(errors.New("no models found matching that term")), "expected resolve failure") - - assert.False(t, isLMSResolveFailure(nil), "did not expect resolve failure") - assert.False(t, isLMSResolveFailure(errors.New("exit status 1: write error: disk full")), "did not expect resolve failure") - assert.False(t, isLMSResolveFailure(errors.New("network connection failed")), "did not expect resolve failure") + test := func(name string, err error, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, isLMSResolveFailure(err)) + }) + } + test("missing artifact", errors.New(missingArtifact), true) + test("unsupported model", errors.New("this model is not supported in LM Studio"), true) + test("no matching models", errors.New("no models found matching that term"), true) + test("no error", nil, false) + test("disk full", errors.New("exit status 1: write error: disk full"), false) + test("network failure", errors.New("network connection failed"), false) } func TestIsLMSTransientDownloadError_RecognizesResumableFailures(t *testing.T) { const downloadTimeout = "exit status 1: Error: Download failed: Timed-out. Please try to resume. - You can try to resume the download within LM Studio." - assert.True(t, isLMSTransientDownloadError(errors.New(downloadTimeout)), "expected transient download error") - assert.True(t, isLMSTransientDownloadError(errors.New("exit status 1: read ECONNRESET")), "expected transient download error") - assert.True(t, isLMSTransientDownloadError(errors.New("exit status 1: socket hang up")), "expected transient download error") - assert.True(t, isLMSTransientDownloadError(errors.New("exit status 1: fetch failed")), "expected transient download error") - - assert.False(t, isLMSTransientDownloadError(nil), "did not expect transient download error") - assert.False(t, isLMSTransientDownloadError(errors.New("exit status 1: write error: disk full")), "did not expect transient download error") + test := func(name string, err error, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, isLMSTransientDownloadError(err)) + }) + } + test("download timeout", errors.New(downloadTimeout), true) + test("connection reset", errors.New("exit status 1: read ECONNRESET"), true) + test("socket hang up", errors.New("exit status 1: socket hang up"), true) + test("fetch failed", errors.New("exit status 1: fetch failed"), true) + test("no error", nil, false) + test("disk full", errors.New("exit status 1: write error: disk full"), false) // A resolution failure is permanent for this source (handled by the // candidate loop), so it must NOT be treated as a transient download. - assert.False(t, isLMSTransientDownloadError(errors.New(`exit status 1: Failed to resolve artifact "x/y": the artifact does not exist`)), "did not expect transient download error") + test("permanent resolution failure", errors.New(`exit status 1: Failed to resolve artifact "x/y": the artifact does not exist`), false) } // TestCmdActionLMSGetFallback drives a cmd action that opts into lms-get @@ -207,25 +171,19 @@ func readCount(t *testing.T, path string) int { } func TestModelResolution_RejectsUnsupportedManifestValues(t *testing.T) { - cases := []struct { - name string - action Action - wantErr bool - }{ - {"valid lms-get", Action{Cmd: []string{"{cli}", "get", "{model}", "--yes"}, ModelResolution: "lms-get"}, false}, - {"unknown strategy", Action{Cmd: []string{"{cli}", "get", "{model}"}, ModelResolution: "bogus"}, true}, - {"http cannot resolve models", Action{HTTP: &ActionHTTP{Method: "POST", Path: "/x"}, ModelResolution: "lms-get"}, true}, - {"cmd must template {model}", Action{Cmd: []string{"{cli}", "ls"}, ModelResolution: "lms-get"}, true}, - {"plain cmd still valid", Action{Cmd: []string{"{cli}", "ls"}}, false}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - err := c.action.validate("pull_model") - if c.wantErr { - require.Error(t, err, "expected a validation error") + test := func(name string, action Action, wantErr bool) { + t.Run(name, func(t *testing.T) { + err := action.validate("pull_model") + if wantErr { + require.Error(t, err) } else { require.NoError(t, err) } }) } + test("valid lms-get", Action{Cmd: []string{"{cli}", "get", "{model}", "--yes"}, ModelResolution: "lms-get"}, false) + test("unknown strategy", Action{Cmd: []string{"{cli}", "get", "{model}"}, ModelResolution: "bogus"}, true) + test("http cannot resolve models", Action{HTTP: &ActionHTTP{Method: "POST", Path: "/x"}, ModelResolution: "lms-get"}, true) + test("cmd must template {model}", Action{Cmd: []string{"{cli}", "ls"}, ModelResolution: "lms-get"}, true) + test("plain cmd still valid", Action{Cmd: []string{"{cli}", "ls"}}, false) } diff --git a/services/nvpair-engine-manager/manager_test.go b/services/nvpair-engine-manager/manager_test.go index 19de5421..9afd0ab5 100644 --- a/services/nvpair-engine-manager/manager_test.go +++ b/services/nvpair-engine-manager/manager_test.go @@ -20,33 +20,21 @@ import ( // an in-memory codec and asserts the JSON-RPC error responses for the // failure paths the e2e harness can't easily reach. func TestManager_ReturnsProtocolErrorsForInvalidRequests(t *testing.T) { - newM := func() (*Manager, *bytes.Buffer) { - var out bytes.Buffer - ex := NewExecutor(NewRegistry(), NewReporter(nil), func(string, any) {}, t.TempDir()) - return NewManager(NewCodec(&out), ex, nil), &out + test := func(name, method, params, want string, dispatch func(*Manager, context.Context, *Message)) { + t.Run(name, func(t *testing.T) { + var out bytes.Buffer + ex := NewExecutor(NewRegistry(), NewReporter(nil), func(string, any) {}, t.TempDir()) + m := NewManager(NewCodec(&out), ex, nil) + id := json.RawMessage("1") + dispatch(m, context.Background(), &Message{JSONRPC: "2.0", ID: &id, Method: method, Params: json.RawMessage(params)}) + require.Contains(t, out.String(), want) + }) } - id := json.RawMessage("1") - ctx := context.Background() - - m, out := newM() - m.handleMessage(ctx, &Message{JSONRPC: "2.0", ID: &id, Method: "bogus"}) - mustContain(t, out.String(), "-32601") - - m, out = newM() - m.handleMessage(ctx, &Message{JSONRPC: "2.0", ID: &id, Method: "engine:describe", Params: json.RawMessage(`{"engine":"nope"}`)}) - mustContain(t, out.String(), "unknown engine") - - m, out = newM() - m.runOp(ctx, &Message{JSONRPC: "2.0", ID: &id, Method: "engine:start", Params: json.RawMessage(`{}`)}) - mustContain(t, out.String(), "engine is required") - - m, out = newM() - m.runOp(ctx, &Message{JSONRPC: "2.0", ID: &id, Method: "engine:start", Params: json.RawMessage(`{bad}`)}) - mustContain(t, out.String(), "invalid params") - - m, out = newM() - m.runAction(ctx, &Message{JSONRPC: "2.0", ID: &id, Method: "engine:action", Params: json.RawMessage(`{"engine":"x"}`)}) - mustContain(t, out.String(), "engine and action are required") + test("unknown method", "bogus", "", "-32601", (*Manager).handleMessage) + test("unknown engine", "engine:describe", `{"engine":"nope"}`, "unknown engine", (*Manager).handleMessage) + test("missing engine", "engine:start", `{}`, "engine is required", (*Manager).runOp) + test("invalid params", "engine:start", `{bad}`, "invalid params", (*Manager).runOp) + test("missing action", "engine:action", `{"engine":"x"}`, "engine and action are required", (*Manager).runAction) } func TestRunOp_RejectsBadPort(t *testing.T) { @@ -82,17 +70,8 @@ func TestRunOp_StartsEngineAfterInstallation(t *testing.T) { } func TestStatusQueries_DoNotBlockMessageDispatchDuringEngineOperation(t *testing.T) { - tests := []struct { - name string - method string - params json.RawMessage - }{ - {name: "status", method: "engine:status", params: json.RawMessage(`{"engine":"fake"}`)}, - {name: "get installed", method: "engine:get-installed"}, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { + test := func(name, method string, params json.RawMessage) { + t.Run(name, func(t *testing.T) { ex := newTestExecutor(t, testEngineManifest(fakeEngineBin)) st, err := ex.state("fake") require.NoError(t, err) @@ -116,8 +95,8 @@ func TestStatusQueries_DoNotBlockMessageDispatchDuringEngineOperation(t *testing m.handleMessage(context.Background(), &Message{ JSONRPC: "2.0", ID: &id, - Method: tc.method, - Params: tc.params, + Method: method, + Params: params, }) close(returned) }() @@ -127,7 +106,7 @@ func TestStatusQueries_DoNotBlockMessageDispatchDuringEngineOperation(t *testing // Expected: the potentially blocking status work was dispatched. case <-time.After(time.Second): st.opMu.Unlock() - require.FailNowf(t, "message dispatch blocked while an engine operation held opMu", "method %s", tc.method) + require.FailNow(t, "message dispatch blocked while an engine operation held opMu") } st.opMu.Unlock() @@ -135,10 +114,12 @@ func TestStatusQueries_DoNotBlockMessageDispatchDuringEngineOperation(t *testing case line := <-response: require.Contains(t, line, `"id":1`) case <-time.After(2 * time.Second): - require.FailNowf(t, "no response after the engine operation completed", "method %s", tc.method) + require.FailNow(t, "no response after the engine operation completed") } }) } + test("status", "engine:status", json.RawMessage(`{"engine":"fake"}`)) + test("get installed", "engine:get-installed", nil) } func mustContain(t *testing.T, s, sub string) { diff --git a/services/nvpair-engine-manager/models_gate_test.go b/services/nvpair-engine-manager/models_gate_test.go index c937f9b0..66e7f068 100644 --- a/services/nvpair-engine-manager/models_gate_test.go +++ b/services/nvpair-engine-manager/models_gate_test.go @@ -84,21 +84,19 @@ func okHandler() http.Handler { func TestModelSurface_RestrictsPlaintextToLoopback(t *testing.T) { h := loopbackOnly(okHandler()) - for _, remote := range []string{"127.0.0.1:51234", "[::1]:51234"} { - rec := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, modelsPath, nil) - req.RemoteAddr = remote - h(rec, req) - require.Equal(t, http.StatusOK, rec.Code, "loopback (%v)", remote) - } - - for _, remote := range []string{"192.168.1.42:51234", "10.0.0.7:51234"} { - rec := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, modelsPath, nil) - req.RemoteAddr = remote - h(rec, req) - require.Equal(t, http.StatusForbidden, rec.Code, "LAN (%v)", remote) + test := func(name, remote string, wantStatus int) { + t.Run(name, func(t *testing.T) { + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, modelsPath, nil) + req.RemoteAddr = remote + h(rec, req) + require.Equal(t, wantStatus, rec.Code) + }) } + test("IPv4 loopback", "127.0.0.1:51234", http.StatusOK) + test("IPv6 loopback", "[::1]:51234", http.StatusOK) + test("private LAN", "192.168.1.42:51234", http.StatusForbidden) + test("private network", "10.0.0.7:51234", http.StatusForbidden) } // TestModelSurface_PinGateIsUnconditional: the mTLS personality admits a pinned @@ -141,13 +139,17 @@ func TestModelSurface_RequiresPinnedPeerForEveryClusterRequest(t *testing.T) { // An unauthenticated request never carries a client cert, so it is refused // whatever this node's membership is — the gate has no membership branch. - for _, name := range []string{"clustered", "unclustered"} { - mesh := selfMesh - if name == "unclustered" { - mesh = clustertrust.Open(t.TempDir()) - } - rec := httptest.NewRecorder() - requirePinnedPeer(mesh, okHandler())(rec, httptest.NewRequest(http.MethodGet, modelsPath, nil)) - require.Equal(t, http.StatusForbidden, rec.Code, " (%v)", name) + test := func(name string, clustered bool) { + t.Run(name, func(t *testing.T) { + mesh := selfMesh + if !clustered { + mesh = clustertrust.Open(t.TempDir()) + } + rec := httptest.NewRecorder() + requirePinnedPeer(mesh, okHandler())(rec, httptest.NewRequest(http.MethodGet, modelsPath, nil)) + require.Equal(t, http.StatusForbidden, rec.Code) + }) } + test("clustered", true) + test("unclustered", false) } diff --git a/services/nvpair-engine-manager/models_test.go b/services/nvpair-engine-manager/models_test.go index 5c1e10da..305a7059 100644 --- a/services/nvpair-engine-manager/models_test.go +++ b/services/nvpair-engine-manager/models_test.go @@ -109,18 +109,19 @@ func TestExtractStringsResult_DistinguishesEmptyFromUnknown(t *testing.T) { require.True(t, ok, "explicit empty inventory (%v, %v)", got, ok) require.NotNil(t, got, "explicit empty inventory must serialize as []") assert.Empty(t, got, "explicit empty inventory") - for _, raw := range []string{ - `{}`, - `{"models":null}`, - `{"models":{}}`, - `{"models":[{}]}`, - `{"models":[null]}`, - `not-json`, - } { - got, ok := extractStringsResult(json.RawMessage(raw), spec) - assert.False(t, ok, "invalid inventory (%v, %v, %v)", raw, got, ok) - assert.Nil(t, got, "invalid inventory (%v)", raw) + test := func(name, raw string) { + t.Run(name, func(t *testing.T) { + got, ok := extractStringsResult(json.RawMessage(raw), spec) + assert.False(t, ok, "invalid inventory must remain unknown") + assert.Nil(t, got, "invalid inventory must not return models") + }) } + test("missing models", `{}`) + test("null models", `{"models":null}`) + test("object models", `{"models":{}}`) + test("missing model key", `{"models":[{}]}`) + test("null model entry", `{"models":[null]}`) + test("invalid JSON", `not-json`) } // TestModels drives Models() against the running fake engine: a running engine @@ -288,43 +289,31 @@ func TestSweepLoaded_RetainsLastGoodOnTransientMiss(t *testing.T) { } func TestChangedEngines_IdentifiesInventoryDifferences(t *testing.T) { - tests := []struct { - name string - prev, cur map[string][]string - want []string - }{ - {name: "no change", prev: map[string][]string{"a": {"x"}}, cur: map[string][]string{"a": {"x"}}, want: nil}, - {name: "reorder is not a change", prev: map[string][]string{"a": {"x", "y"}}, cur: map[string][]string{"a": {"y", "x"}}, want: nil}, - {name: "value changed", prev: map[string][]string{"a": {"x"}}, cur: map[string][]string{"a": {"x", "y"}}, want: []string{"a"}}, - {name: "new key reported", prev: map[string][]string{}, cur: map[string][]string{"a": {"x"}}, want: []string{"a"}}, - {name: "disappeared key not reported", prev: map[string][]string{"a": {"x"}, "b": {"y"}}, cur: map[string][]string{"a": {"x"}}, want: nil}, - {name: "present-empty vs nil is not a change", prev: map[string][]string{"a": {}}, cur: map[string][]string{"a": nil}, want: nil}, - {name: "multiple changed sorted", prev: nil, cur: map[string][]string{"b": {"1"}, "a": {"2"}}, want: []string{"a", "b"}}, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.want, changedEngines(tc.prev, tc.cur), "changedEngines") + test := func(name string, prev, cur map[string][]string, want []string) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, changedEngines(prev, cur)) }) } + test("no change", map[string][]string{"a": {"x"}}, map[string][]string{"a": {"x"}}, nil) + test("reorder is not a change", map[string][]string{"a": {"x", "y"}}, map[string][]string{"a": {"y", "x"}}, nil) + test("value changed", map[string][]string{"a": {"x"}}, map[string][]string{"a": {"x", "y"}}, []string{"a"}) + test("new key reported", map[string][]string{}, map[string][]string{"a": {"x"}}, []string{"a"}) + test("disappeared key not reported", map[string][]string{"a": {"x"}, "b": {"y"}}, map[string][]string{"a": {"x"}}, nil) + test("present-empty vs nil is not a change", map[string][]string{"a": {}}, map[string][]string{"a": nil}, nil) + test("multiple changed sorted", nil, map[string][]string{"b": {"1"}, "a": {"2"}}, []string{"a", "b"}) } func TestSameStringSet_IgnoresOrdering(t *testing.T) { - tests := []struct { - name string - a, b []string - want bool - }{ - {name: "nil equals empty", a: nil, b: []string{}, want: true}, - {name: "same order", a: []string{"x", "y"}, b: []string{"x", "y"}, want: true}, - {name: "different order", a: []string{"x", "y"}, b: []string{"y", "x"}, want: true}, - {name: "different length", a: []string{"x"}, b: []string{"x", "y"}, want: false}, - {name: "different elements", a: []string{"x"}, b: []string{"y"}, want: false}, - {name: "duplicates matter", a: []string{"x", "x"}, b: []string{"x", "y"}, want: false}, - {name: "same multiset with dups", a: []string{"x", "x", "y"}, b: []string{"y", "x", "x"}, want: true}, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.want, sameStringSet(tc.a, tc.b), "sameStringSet") + test := func(name string, a, b []string, want bool) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, sameStringSet(a, b)) }) } + test("nil equals empty", nil, []string{}, true) + test("same order", []string{"x", "y"}, []string{"x", "y"}, true) + test("different order", []string{"x", "y"}, []string{"y", "x"}, true) + test("different length", []string{"x"}, []string{"x", "y"}, false) + test("different elements", []string{"x"}, []string{"y"}, false) + test("duplicates matter", []string{"x", "x"}, []string{"x", "y"}, false) + test("same multiset with dups", []string{"x", "x", "y"}, []string{"y", "x", "x"}, true) } diff --git a/services/nvpair-engine-manager/modelsdir_test.go b/services/nvpair-engine-manager/modelsdir_test.go index f911fe7e..d86c8778 100644 --- a/services/nvpair-engine-manager/modelsdir_test.go +++ b/services/nvpair-engine-manager/modelsdir_test.go @@ -96,46 +96,43 @@ func destructiveToken(arg string) string { // libraries have to be recognised, or the guard documents a protection it does // not provide. func TestDestructiveToken_RecognizesOriginalUnsafeUninstallCommands(t *testing.T) { - originals := map[string]string{ - "posix": `pkill -x lms 2>/dev/null; pkill -x llmster 2>/dev/null; sleep 2; rm -rf "$HOME/.lmstudio"; sleep 1; [ -d "$HOME/.lmstudio" ] && exit 1; exit 0`, - "windows": `$root = Join-Path $env:USERPROFILE '.lmstudio'; Start-Sleep -Seconds 3; ` + - `Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue`, - } - for name, argument := range originals { - assert.NotEmpty(t, destructiveToken(argument), "%s: the original LM Studio uninstall was not recognised as deleting files", name) - } - for _, harmless := range []string{ - "pkill -x lms 2>/dev/null; pkill -x llmster 2>/dev/null; sleep 2; exit 0", - "server", - "--strip-components=1", - } { - assert.Empty(t, destructiveToken(harmless), "argument %q deletes nothing", harmless) + test := func(name, argument string, destructive bool) { + t.Run(name, func(t *testing.T) { + if destructive { + assert.NotEmpty(t, destructiveToken(argument), "the original LM Studio uninstall was not recognised as deleting files") + } else { + assert.Empty(t, destructiveToken(argument), "harmless argument must not be treated as deleting files") + } + }) } + test("posix uninstall", `pkill -x lms 2>/dev/null; pkill -x llmster 2>/dev/null; sleep 2; rm -rf "$HOME/.lmstudio"; sleep 1; [ -d "$HOME/.lmstudio" ] && exit 1; exit 0`, true) + test("windows uninstall", `$root = Join-Path $env:USERPROFILE '.lmstudio'; Start-Sleep -Seconds 3; Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue`, true) + test("stop processes", "pkill -x lms 2>/dev/null; pkill -x llmster 2>/dev/null; sleep 2; exit 0", false) + test("server command", "server", false) + test("extraction flag", "--strip-components=1", false) } // TestValidateRejectsRemovalsThatReachTheModelStore pins the load-time // rejections the manifest reference promises. Each of these was accepted before, // and each deletes part or all of a user's model library at uninstall time. func TestValidate_RejectsRemovalsThatReachTheModelStore(t *testing.T) { - for name, uninstall := range map[string]struct { - modelsDir string - remove []string - }{ - "no store to preserve": {modelsDir: "", remove: []string{"~/.engine"}}, - "the store itself": {modelsDir: "~/.engine/models", remove: []string{"~/.engine/models"}}, - "inside the store": {modelsDir: "~/.engine/models", remove: []string{"~/.engine/models/publisher"}}, - "the store, templated": {modelsDir: "~/.engine/models", remove: []string{"{models_dir}"}}, - "inside it, templated": {modelsDir: "~/.engine/models", remove: []string{"{models_dir}/publisher"}}, - "the store, mixed case": {modelsDir: "~/.engine/models", remove: []string{"~/.engine/Models"}}, - } { - platform := Platform{ - Detect: []string{"{install_dir}/engine"}, - ModelsDir: uninstall.modelsDir, - Uninstall: &Uninstall{Remove: uninstall.remove}, - Runtime: Runtime{Bin: "{install_dir}/engine"}, - } - assert.Error(t, platform.validate(runtime.GOOS+"/"+runtime.GOARCH), "%s: accepted uninstall.remove %v with models_dir %q", name, uninstall.remove, uninstall.modelsDir) + test := func(name, modelsDir string, remove []string) { + t.Run(name, func(t *testing.T) { + platform := Platform{ + Detect: []string{"{install_dir}/engine"}, + ModelsDir: modelsDir, + Uninstall: &Uninstall{Remove: remove}, + Runtime: Runtime{Bin: "{install_dir}/engine"}, + } + assert.Error(t, platform.validate(runtime.GOOS+"/"+runtime.GOARCH), "uninstall.remove must preserve the model store") + }) } + test("no store to preserve", "", []string{"~/.engine"}) + test("the store itself", "~/.engine/models", []string{"~/.engine/models"}) + test("inside the store", "~/.engine/models", []string{"~/.engine/models/publisher"}) + test("the store, templated", "~/.engine/models", []string{"{models_dir}"}) + test("inside it, templated", "~/.engine/models", []string{"{models_dir}/publisher"}) + test("the store, mixed case", "~/.engine/models", []string{"~/.engine/Models"}) } func TestRemoveTreePreserving_KeepsNestedStore(t *testing.T) { diff --git a/services/nvpair-engine-manager/portoverride_test.go b/services/nvpair-engine-manager/portoverride_test.go index b440a705..0437954d 100644 --- a/services/nvpair-engine-manager/portoverride_test.go +++ b/services/nvpair-engine-manager/portoverride_test.go @@ -88,12 +88,8 @@ func TestPersistPort_OverridesBundledPlatformPort(t *testing.T) { } func TestPersistPort_RefusesMalformedOverrideWithoutClobbering(t *testing.T) { - for _, data := range []string{ - `{`, `null`, `[]`, `{}`, `{"runtime":{}}`, `{"engine":"another-engine"}`, - `{"engine":"ollama","runtime":null}`, - `{"engine":"ollama","platforms":[]}`, - } { - t.Run(data, func(t *testing.T) { + test := func(name, data string) { + t.Run(name, func(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "ollama.json") ex := newBundledExecutor(t, dir) @@ -101,12 +97,20 @@ func TestPersistPort_RefusesMalformedOverrideWithoutClobbering(t *testing.T) { _, err := ex.SetPort(context.Background(), "ollama", 23001) require.Error(t, err, "malformed override was overwritten") got, err := os.ReadFile(path) - require.NoError(t, err, "invalid override changed (%v, %v)", got, err) + require.NoError(t, err, "read invalid override") require.Equal(t, data, string(got), "invalid override changed") status, _ := ex.Status("ollama") require.Equal(t, 11434, status.Port, "failed persistence changed runtime port (%v)", status) }) } + test("invalid JSON", `{`) + test("null override", `null`) + test("array override", `[]`) + test("missing engine", `{}`) + test("runtime without engine", `{"runtime":{}}`) + test("wrong engine", `{"engine":"another-engine"}`) + test("null runtime", `{"engine":"ollama","runtime":null}`) + test("array platforms", `{"engine":"ollama","platforms":[]}`) } func TestWriteJSONAtomic_ReplacesExistingFile(t *testing.T) { diff --git a/services/nvpair-engine-manager/proc_test.go b/services/nvpair-engine-manager/proc_test.go index 02941639..d744adb1 100644 --- a/services/nvpair-engine-manager/proc_test.go +++ b/services/nvpair-engine-manager/proc_test.go @@ -13,19 +13,16 @@ import ( func TestIsOurEngineImage_RecognizesManagedExecutable(t *testing.T) { bin := filepath.Join("/opt", "nvpair", "ollama.exe") - cases := []struct { - image string - want bool - }{ - {bin, true}, - {`/opt/nvpair/OLLAMA.EXE`, true}, - {bin + ` (deleted)`, true}, - {`/opt/nvpair/other.exe`, false}, - {``, false}, - } - for _, tc := range cases { - assert.Equal(t, tc.want, isOurEngineImage(tc.image, bin), "isOurEngineImage (%v)", bin) + test := func(name, image string, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, isOurEngineImage(image, bin)) + }) } + test("matching binary", bin, true) + test("case insensitive", `/opt/nvpair/OLLAMA.EXE`, true) + test("deleted binary", bin+` (deleted)`, true) + test("other binary", `/opt/nvpair/other.exe`, false) + test("empty image", ``, false) require.False(t, isOurEngineImage(bin, ``), "empty binPath must not match") } diff --git a/services/nvpair-engine-manager/pull_llamacpp_test.go b/services/nvpair-engine-manager/pull_llamacpp_test.go index 0df2eddf..11e67d8b 100644 --- a/services/nvpair-engine-manager/pull_llamacpp_test.go +++ b/services/nvpair-engine-manager/pull_llamacpp_test.go @@ -247,14 +247,10 @@ func TestLlamaCPPPullCancelledBeforeStart_DoesNotDownload(t *testing.T) { } func TestLlamaCPPPull_RejectsInvalidModelParamsBeforeStarting(t *testing.T) { - for _, tc := range []struct{ name, params string }{ - {"different model", `{"model":"other/model"}`}, - {"missing model", `{}`}, - {"invalid JSON", `{"model":`}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name, params string) { + t.Run(name, func(t *testing.T) { f := newLlamaCPPPullFixture(t) - _, err := f.ex.PullModelStream(context.Background(), "fake", llamaCPPPullTestModel, json.RawMessage(tc.params)) + _, err := f.ex.PullModelStream(context.Background(), "fake", llamaCPPPullTestModel, json.RawMessage(params)) require.Error(t, err, "invalid model params were accepted") select { case <-f.started: @@ -265,6 +261,9 @@ func TestLlamaCPPPull_RejectsInvalidModelParamsBeforeStarting(t *testing.T) { require.Zero(t, f.unloads.Load(), "invalid params triggered cleanup") }) } + test("different model", `{"model":"other/model"}`) + test("missing model", `{}`) + test("invalid JSON", `{"model":`) } func TestLlamaCPPPull_PreservesOtherDownloadWhenStartIsRejected(t *testing.T) { @@ -334,7 +333,7 @@ func TestLlamaCPPPullUnconfirmedStart_DoesNotUnload(t *testing.T) { } func TestLlamaCPPPull_SkipsCleanupAfterTerminalEvents(t *testing.T) { - for _, event := range []string{"download_finished", "download_failed"} { + test := func(event string) { t.Run(event, func(t *testing.T) { f := newLlamaCPPPullFixture(t) f.stream = func(w http.ResponseWriter, r *http.Request) { @@ -359,10 +358,12 @@ func TestLlamaCPPPull_SkipsCleanupAfterTerminalEvents(t *testing.T) { require.Zero(t, f.inventories.Load(), "terminal event triggered cleanup") }) } + test("download_finished") + test("download_failed") } func TestLlamaCPPPullCleanup_PreservesCompletedModels(t *testing.T) { - for _, status := range []string{"downloaded", "loaded", "unloaded", "missing"} { + test := func(status string) { t.Run(status, func(t *testing.T) { f := newLlamaCPPPullFixture(t) close(f.endStream) @@ -382,6 +383,10 @@ func TestLlamaCPPPullCleanup_PreservesCompletedModels(t *testing.T) { require.Equal(t, int32(1), f.inventories.Load()) }) } + test("downloaded") + test("loaded") + test("unloaded") + test("missing") } func TestLlamaCPPPullCleanupFailure_PreservesCancellation(t *testing.T) { @@ -414,7 +419,7 @@ func TestLlamaCPPPullCleanupFailure_PreservesCancellation(t *testing.T) { } func TestLlamaCPPPullCleanupTimeout_PreservesWatchdogCause(t *testing.T) { - for _, route := range []string{"inventory", "unload"} { + test := func(route string) { t.Run(route, func(t *testing.T) { f := newLlamaCPPPullFixture(t) f.ex.pullProgressTimeout = 100 * time.Millisecond @@ -432,11 +437,13 @@ func TestLlamaCPPPullCleanupTimeout_PreservesWatchdogCause(t *testing.T) { require.True(t, f.downloading.Load(), "cleanup timeout was treated as a confirmed stop") }) } + test("inventory") + test("unload") } func TestLlamaCPPPullInvalidInventory_DoesNotConfirmStop(t *testing.T) { - for _, body := range []string{"not JSON", `{}`, `{"data":null}`, `{"data":{}}`, fmt.Sprintf(`{"data":[{"id":%q}]}`, llamaCPPPullTestModel)} { - t.Run(body, func(t *testing.T) { + test := func(name, body string) { + t.Run(name, func(t *testing.T) { f := newLlamaCPPPullFixture(t) close(f.endStream) f.inventory = func(w http.ResponseWriter, _ *http.Request) { @@ -448,4 +455,9 @@ func TestLlamaCPPPullInvalidInventory_DoesNotConfirmStop(t *testing.T) { require.Zero(t, f.unloads.Load(), "invalid inventory must not confirm a stop") }) } + test("invalid JSON", "not JSON") + test("missing data", `{}`) + test("null data", `{"data":null}`) + test("object data", `{"data":{}}`) + test("missing status", fmt.Sprintf(`{"data":[{"id":%q}]}`, llamaCPPPullTestModel)) } diff --git a/services/nvpair-engine-manager/pull_test.go b/services/nvpair-engine-manager/pull_test.go index 5ae5b577..e2a413e7 100644 --- a/services/nvpair-engine-manager/pull_test.go +++ b/services/nvpair-engine-manager/pull_test.go @@ -45,18 +45,16 @@ func TestHandlePull_RejectsMissingTarget(t *testing.T) { } func TestModelFromParams_ExtractsRequestedModel(t *testing.T) { - cases := []struct { - params, want string - }{ - {`{"name":"llama3.2"}`, "llama3.2"}, // Ollama body key - {`{"model":"owner/repo"}`, "owner/repo"}, // generic placeholder - {`{"name":"a","model":"b"}`, "a"}, // name wins - {`{}`, ""}, // neither present - {``, ""}, // empty params - } - for _, c := range cases { - require.Equal(t, c.want, modelFromParams([]byte(c.params)), "modelFromParams") + test := func(name, params, want string) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, modelFromParams([]byte(params))) + }) } + test("Ollama body key", `{"name":"llama3.2"}`, "llama3.2") + test("generic placeholder", `{"model":"owner/repo"}`, "owner/repo") + test("name wins", `{"name":"a","model":"b"}`, "a") + test("neither present", `{}`, "") + test("empty params", ``, "") } func TestLlamaCPPModelsEventPercent_AggregatesFiles(t *testing.T) { diff --git a/services/nvpair-engine-manager/registry_test.go b/services/nvpair-engine-manager/registry_test.go index ab349423..fedd8985 100644 --- a/services/nvpair-engine-manager/registry_test.go +++ b/services/nvpair-engine-manager/registry_test.go @@ -208,11 +208,20 @@ func setInstallArtifacts(m *Manifest, artifacts []InstallArtifact) { } func TestValidate_RejectsBadEngineName(t *testing.T) { - for _, bad := range []string{"../evil", "a/b", `a\b`, "..", ".", "a b", ""} { - m := validManifest() - m.Engine = bad - assert.Error(t, m.Validate(), "expected rejection of engine name (%v)", bad) + test := func(name, engine string) { + t.Run(name, func(t *testing.T) { + m := validManifest() + m.Engine = engine + assert.Error(t, m.Validate(), "invalid engine name must be rejected") + }) } + test("parent traversal", "../evil") + test("forward slash", "a/b") + test("backslash", `a\b`) + test("parent directory", "..") + test("current directory", ".") + test("space", "a b") + test("empty name", "") } func TestValidate_RejectsScriptWithFetch(t *testing.T) { diff --git a/services/nvpair-engine-manager/remediation_test.go b/services/nvpair-engine-manager/remediation_test.go index e62336c8..233a0562 100644 --- a/services/nvpair-engine-manager/remediation_test.go +++ b/services/nvpair-engine-manager/remediation_test.go @@ -343,25 +343,21 @@ func TestAction_ReportsUnknownActionAndHTTPFailure(t *testing.T) { func TestExpandPath_ResolvesHomeAndEnvironmentVariables(t *testing.T) { t.Setenv("NVPAIR_TEST_VAR", "xyz") - for _, tc := range []struct { - name, goos, input, want string - }{ - {"windows percent", "windows", "a/%NVPAIR_TEST_VAR%/b", "a/xyz/b"}, - {"windows dollar", "windows", "a/$NVPAIR_TEST_VAR/b", "a/$NVPAIR_TEST_VAR/b"}, - {"windows braced dollar", "windows", "a/${NVPAIR_TEST_VAR}/b", "a/${NVPAIR_TEST_VAR}/b"}, - {"unix dollar", "linux", "a/$NVPAIR_TEST_VAR/b", "a/xyz/b"}, - {"unix braced dollar", "linux", "a/${NVPAIR_TEST_VAR}/b", "a/xyz/b"}, - {"unix percent", "linux", "a/%NVPAIR_TEST_VAR%/b", "a/%NVPAIR_TEST_VAR%/b"}, - {"unix shell parameters", "linux", `tar -xzf "$1" -C "$3"`, `tar -xzf "$1" -C "$3"`}, - } { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, expandPathForOS(tc.input, tc.goos), "expandPathForOS") + test := func(name, goos, input, want string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, expandPathForOS(input, goos)) }) } + test("windows percent", "windows", "a/%NVPAIR_TEST_VAR%/b", "a/xyz/b") + test("windows dollar", "windows", "a/$NVPAIR_TEST_VAR/b", "a/$NVPAIR_TEST_VAR/b") + test("windows braced dollar", "windows", "a/${NVPAIR_TEST_VAR}/b", "a/${NVPAIR_TEST_VAR}/b") + test("unix dollar", "linux", "a/$NVPAIR_TEST_VAR/b", "a/xyz/b") + test("unix braced dollar", "linux", "a/${NVPAIR_TEST_VAR}/b", "a/xyz/b") + test("unix percent", "linux", "a/%NVPAIR_TEST_VAR%/b", "a/%NVPAIR_TEST_VAR%/b") + test("unix shell parameters", "linux", `tar -xzf "$1" -C "$3"`, `tar -xzf "$1" -C "$3"`) if home, err := os.UserHomeDir(); err == nil { - for _, goos := range []string{"windows", "linux"} { - assert.Equal(t, filepath.Join(home, "sub"), expandPathForOS("~/sub", goos), " (%v)", goos) - } + test("windows home", "windows", "~/sub", filepath.Join(home, "sub")) + test("unix home", "linux", "~/sub", filepath.Join(home, "sub")) } } diff --git a/services/nvpair-engine-manager/remoteclient_test.go b/services/nvpair-engine-manager/remoteclient_test.go index 9074da5c..e480b7af 100644 --- a/services/nvpair-engine-manager/remoteclient_test.go +++ b/services/nvpair-engine-manager/remoteclient_test.go @@ -66,22 +66,18 @@ func TestRemoteModelOperations_AllowReadinessResponseHeaderBudget(t *testing.T) func TestRemoteReadinessBudget_CoversEngineStartupAllowance(t *testing.T) { require.Greater(t, remoteReadyResponseHeaderTimeout, remoteResponseHeaderTimeout, "readiness budget") - cases := []struct { - path string - engine string - want bool - }{ - {controlStartPath, "ollama", true}, - {controlDeletePath, "lmstudio", true}, - {controlLoadPath, "ollama", true}, - {controlLoadPath, "lmstudio", false}, - {controlStopPath, "ollama", false}, - {controlUnloadPath, "ollama", false}, - {controlEnginesPath, "", false}, - } - for _, tc := range cases { - assert.Equal(t, tc.want, waitsForEngineReadiness(tc.path, tc.engine), "waitsForEngineReadiness") + test := func(name, path, engine string, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, waitsForEngineReadiness(path, engine)) + }) } + test("start waits", controlStartPath, "ollama", true) + test("delete waits", controlDeletePath, "lmstudio", true) + test("Ollama load waits", controlLoadPath, "ollama", true) + test("LM Studio load does not wait", controlLoadPath, "lmstudio", false) + test("stop does not wait", controlStopPath, "ollama", false) + test("unload does not wait", controlUnloadPath, "ollama", false) + test("listing does not wait", controlEnginesPath, "", false) } func TestRemoteStartHeaderWait_RemainsBounded(t *testing.T) { diff --git a/services/nvpair-engine-manager/setport_test.go b/services/nvpair-engine-manager/setport_test.go index 94e45890..a43e8ffb 100644 --- a/services/nvpair-engine-manager/setport_test.go +++ b/services/nvpair-engine-manager/setport_test.go @@ -127,52 +127,52 @@ func TestOverrideDeepMerge_PreservesBundledFields(t *testing.T) { // a bundled engine that isn't running: the chosen port is written as a // manifest override and a fresh (startup-style) load comes back up on it. func TestSetPort_PersistsAndRestores(t *testing.T) { - for _, tc := range []struct { - engine string - port int - bundled int - }{ - {"ollama", 12345, 11434}, - {"lmstudio", 4321, 1235}, - } { - t.Run(tc.engine, func(t *testing.T) { + test := func(engine string, port, bundled int) { + t.Run(engine, func(t *testing.T) { dir := t.TempDir() ex := newBundledExecutor(t, dir) - st, err := ex.SetPort(context.Background(), tc.engine, tc.port) + st, err := ex.SetPort(context.Background(), engine, port) require.NoError(t, err, "SetPort") - assert.Equal(t, tc.port, st.Port, "returned status port") + assert.Equal(t, port, st.Port, "returned status port") // The override file is the minimal port delta. - data, err := os.ReadFile(filepath.Join(dir, tc.engine+".json")) + data, err := os.ReadFile(filepath.Join(dir, engine+".json")) require.NoError(t, err, "override file not written") var got map[string]any require.NoError(t, json.Unmarshal(data, &got), "override file invalid JSON") rt, _ := got["runtime"].(map[string]any) if assert.NotNil(t, rt, "override file runtime.port:") { - assert.Equal(t, tc.port, int(rt["port"].(float64)), "override file runtime.port") + assert.Equal(t, port, int(rt["port"].(float64)), "override file runtime.port") } // Restore: a fresh startup-style load comes up on the chosen port. - assert.Equal(t, tc.port, hostPort(t, loadWithOverrides(t, dir), tc.engine), "restored port") + assert.Equal(t, port, hostPort(t, loadWithOverrides(t, dir), engine), "restored port") // Reverting to the bundled default removes the override entirely. - _, err = ex.SetPort(context.Background(), tc.engine, tc.bundled) + _, err = ex.SetPort(context.Background(), engine, bundled) require.NoError(t, err, "SetPort revert") - _, err = os.Stat(filepath.Join(dir, tc.engine+".json")) + _, err = os.Stat(filepath.Join(dir, engine+".json")) assert.ErrorIs(t, err, os.ErrNotExist, "override file should be removed when reverting to default") - assert.Equal(t, tc.bundled, hostPort(t, loadWithOverrides(t, dir), tc.engine), "port after revert") + assert.Equal(t, bundled, hostPort(t, loadWithOverrides(t, dir), engine), "port after revert") }) } + test("ollama", 12345, 11434) + test("lmstudio", 4321, 1235) } // TestSetPortRejectsOutOfRange guards the validation boundary. func TestSetPort_RejectsOutOfRange(t *testing.T) { ex := newBundledExecutor(t, t.TempDir()) - for _, bad := range []int{0, -1, 70000} { - _, err := ex.SetPort(context.Background(), "ollama", bad) - assert.Error(t, err, "SetPort (%v)", bad) + test := func(name string, port int) { + t.Run(name, func(t *testing.T) { + _, err := ex.SetPort(context.Background(), "ollama", port) + assert.Error(t, err) + }) } + test("zero", 0) + test("negative", -1) + test("above maximum", 70000) } func TestSetPort_MovesAdoptedCommandEngine(t *testing.T) { diff --git a/services/nvpair-engine-manager/settings_test.go b/services/nvpair-engine-manager/settings_test.go index 0c00c5a3..964664e7 100644 --- a/services/nvpair-engine-manager/settings_test.go +++ b/services/nvpair-engine-manager/settings_test.go @@ -144,18 +144,20 @@ func TestSettingsPreview_ReportsPortConflict(t *testing.T) { func TestSettingsPreview_ChecksOnlyDeclaredControls(t *testing.T) { eachEngineMode(t, func(t *testing.T, e *Executor) { - for _, suffix := range []string{" | other", " && other"} { - p := settingsRequest(t, e) - p.Settings.LaunchText += suffix - require.NotEqual(t, "", previewSettings(t, e, p).Errors["launchText"], "accepted shell syntax (%v)", suffix) + test := func(name, suffix string) { + t.Run(name, func(t *testing.T) { + p := settingsRequest(t, e) + p.Settings.LaunchText += suffix + require.NotEmpty(t, previewSettings(t, e, p).Errors["launchText"], "invalid launch syntax accepted") + }) } + test("shell pipe", " | other") + test("shell conjunction", " && other") policy := settingsState(t, e).plat.Runtime.EditableLaunch if policy.Controls[0].Value == "{server.port}" { - for _, suffix := range []string{" --bind 0.0.0.0", " --port 0", " -- --bind 0.0.0.0"} { - p := settingsRequest(t, e) - p.Settings.LaunchText += suffix - require.NotEqual(t, "", previewSettings(t, e, p).Errors["launchText"], "accepted invalid managed control (%v)", suffix) - } + test("external bind", " --bind 0.0.0.0") + test("zero port", " --port 0") + test("external bind after terminator", " -- --bind 0.0.0.0") } else { p := settingsRequest(t, e) p.Settings.LaunchText = "OLLAMA_HOST=0.0.0.0:12345" @@ -608,13 +610,20 @@ func TestSavedLaunch_OverridesManifestEnvironmentLiterally(t *testing.T) { EditableLaunch: &EditableLaunch{FixedArgs: []string{"serve"}}, Env: map[string]string{"LD_LIBRARY_PATH": "{install_dir}/lib"}, } - rt.LaunchArgs = []string{} - for _, value := range []string{"/custom", "", "{install_dir}/other"} { - rt.LaunchEnv = []string{"LD_LIBRARY_PATH=" + value} - launch, err := resolveProcessLaunch(rt, "engine", map[string]string{"install_dir": "/default", "host": "127.0.0.1", "port": "12345"}) - assert.NoError(t, err, "saved launch environment (%v, %v)", err, value) - assert.Equal(t, value, launch.Env["LD_LIBRARY_PATH"], "saved launch environment (%v)", err) + args := []string{} + rt.LaunchArgs = args + test := func(name, value string) { + t.Run(name, func(t *testing.T) { + env := []string{"LD_LIBRARY_PATH=" + value} + rt.LaunchEnv = env + launch, err := resolveProcessLaunch(rt, "engine", map[string]string{"install_dir": "/default", "host": "127.0.0.1", "port": "12345"}) + assert.NoError(t, err, "saved launch environment") + assert.Equal(t, value, launch.Env["LD_LIBRARY_PATH"], "saved launch environment") + }) } + test("custom directory", "/custom") + test("empty value", "") + test("template remains literal", "{install_dir}/other") } func TestSettings_SwapsLiveServerAndProxyPorts(t *testing.T) { @@ -686,8 +695,8 @@ func TestSettingsCommandLaunch_PreservesLiteralsAndCleansFailedStart(t *testing. } func TestSettingsEnvironment_ReachesEngineAndCanBeEditedAndRemoved(t *testing.T) { - for _, command := range []bool{false, true} { - t.Run(fmt.Sprint(command), func(t *testing.T) { + test := func(name string, command bool) { + t.Run(name, func(t *testing.T) { e := settingsExecutor(t, command) st := settingsState(t, e) if command { @@ -738,17 +747,24 @@ func TestSettingsEnvironment_ReachesEngineAndCanBeEditedAndRemoved(t *testing.T) require.Equal(t, `$HOME {port} C:\new\tools`, env["PAIR_TEST_LITERAL"], "unrelated environment values lost (%v)", env) }) } + test("process", false) + test("command", true) } func TestSettings_RejectsInvalidEnvironmentAssignments(t *testing.T) { e := settingsExecutor(t, false) - for _, prefix := range []string{"9BAD=value", "BAD-NAME=value", "PAIR_TEST=x PAIR_TEST=y"} { - p := settingsRequest(t, e) - p.Settings.LaunchText = prefix + " " + p.Settings.LaunchText - result, err := e.PreviewLaunch(p) - require.NoError(t, err, "invalid assignment accepted (%v, %v)", result, err) - require.NotEmpty(t, result.Errors, "invalid assignment accepted (%v, %v)", result, err) + test := func(name, prefix string) { + t.Run(name, func(t *testing.T) { + p := settingsRequest(t, e) + p.Settings.LaunchText = prefix + " " + p.Settings.LaunchText + result, err := e.PreviewLaunch(p) + require.NoError(t, err) + require.NotEmpty(t, result.Errors, "invalid assignment accepted") + }) } + test("leading digit", "9BAD=value") + test("hyphenated name", "BAD-NAME=value") + test("duplicate assignment", "PAIR_TEST=x PAIR_TEST=y") } // A stop that fails has to leave the saved configuration alone. Persisting diff --git a/services/nvpair-engine-manager/usererrors_test.go b/services/nvpair-engine-manager/usererrors_test.go index 49defe84..954d54fc 100644 --- a/services/nvpair-engine-manager/usererrors_test.go +++ b/services/nvpair-engine-manager/usererrors_test.go @@ -12,23 +12,14 @@ import ( ) func TestUnwrapPullCause_ExtractsUnderlyingDownloadFailure(t *testing.T) { - cases := []struct { - in, want string - }{ - { - "action command failed: exit status 1: Error: Download failed: Timed-out. Please try to resume.", - "Download failed: Timed-out. Please try to resume.", - }, - {`engine "lmstudio" is not running`, `engine "lmstudio" is not running`}, - {"", ""}, - } - for _, c := range cases { - var err error - if c.in != "" { - err = errors.New(c.in) - } - require.Equal(t, c.want, unwrapPullCause(err), "unwrapPullCause") + test := func(name string, err error, want string) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, unwrapPullCause(err)) + }) } + test("command prefix removed", errors.New("action command failed: exit status 1: Error: Download failed: Timed-out. Please try to resume."), "Download failed: Timed-out. Please try to resume.") + test("engine detail preserved", errors.New(`engine "lmstudio" is not running`), `engine "lmstudio" is not running`) + test("nil error", nil, "") } func TestFormatEnginePullError_ReportsActionableDownloadCause(t *testing.T) { diff --git a/services/nvpair-errors/peersync_test.go b/services/nvpair-errors/peersync_test.go index f87297e4..021b0ddf 100644 --- a/services/nvpair-errors/peersync_test.go +++ b/services/nvpair-errors/peersync_test.go @@ -208,29 +208,23 @@ func mustJSON(t *testing.T, v any) json.RawMessage { // TestPeerHostPorts covers address propagation: preserve every ranked address, // fall back to the hostname, and reject nodes with no usable destination. func TestPeerHostPorts_UsesPublishedAddressesAndServicePort(t *testing.T) { - cases := []struct { - name string - node RawNode - want []string - }{ - {"ip preserved", RawNode{Addresses: []string{"192.168.1.5"}, Host: "h.local.", Port: 14319}, []string{"192.168.1.5:14319"}}, - {"host fallback", RawNode{Host: "h.local.", Port: 14319}, []string{"h.local.:14319"}}, - {"no address", RawNode{Port: 14319}, []string{}}, - {"no port", RawNode{Addresses: []string{"192.168.1.5"}}, nil}, - {"all ipv4 deterministic", RawNode{Addresses: []string{"192.168.1.9", "192.168.1.5"}, Port: 14319}, []string{"192.168.1.5:14319", "192.168.1.9:14319"}}, - {"ipv4 before ipv6", RawNode{Addresses: []string{"2001:db8::1", "192.168.1.5"}, Port: 14319}, []string{"192.168.1.5:14319", "[2001:db8::1]:14319"}}, - // No private range outranks another. Which one a peer can actually be - // reached on is not something its subnet number states, so two private - // addresses tie and the tie is broken deterministically rather than by - // the order the browse happened to resolve them in. - {"private ranges tie", RawNode{Addresses: []string{"10.221.0.9", "192.168.1.5"}, Port: 14319}, []string{"10.221.0.9:14319", "192.168.1.5:14319"}}, - {"ip= TXT leads without dropping others", RawNode{Addresses: []string{"10.221.0.9"}, TXT: []string{"ip=192.168.1.5"}, Port: 14319}, []string{"192.168.1.5:14319", "10.221.0.9:14319"}}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, peerHostPorts(tc.node)) + test := func(name string, node RawNode, want []string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, peerHostPorts(node)) }) } + test("ip preserved", RawNode{Addresses: []string{"192.168.1.5"}, Host: "h.local.", Port: 14319}, []string{"192.168.1.5:14319"}) + test("host fallback", RawNode{Host: "h.local.", Port: 14319}, []string{"h.local.:14319"}) + test("no address", RawNode{Port: 14319}, []string{}) + test("no port", RawNode{Addresses: []string{"192.168.1.5"}}, nil) + test("all ipv4 deterministic", RawNode{Addresses: []string{"192.168.1.9", "192.168.1.5"}, Port: 14319}, []string{"192.168.1.5:14319", "192.168.1.9:14319"}) + test("ipv4 before ipv6", RawNode{Addresses: []string{"2001:db8::1", "192.168.1.5"}, Port: 14319}, []string{"192.168.1.5:14319", "[2001:db8::1]:14319"}) + // No private range outranks another. Which one a peer can actually be + // reached on is not something its subnet number states, so two private + // addresses tie and the tie is broken deterministically rather than by + // the order the browse happened to resolve them in. + test("private ranges tie", RawNode{Addresses: []string{"10.221.0.9", "192.168.1.5"}, Port: 14319}, []string{"10.221.0.9:14319", "192.168.1.5:14319"}) + test("ip= TXT leads without dropping others", RawNode{Addresses: []string{"10.221.0.9"}, TXT: []string{"ip=192.168.1.5"}, Port: 14319}, []string{"192.168.1.5:14319", "10.221.0.9:14319"}) } // TestUnclusteredNodeKeepsNoPeers: a node that belongs to no cluster holds no diff --git a/services/nvpair-job-scheduler/delivery_test.go b/services/nvpair-job-scheduler/delivery_test.go index 97f9a2e7..86c9f3d5 100644 --- a/services/nvpair-job-scheduler/delivery_test.go +++ b/services/nvpair-job-scheduler/delivery_test.go @@ -46,11 +46,7 @@ func (w *recoveringWriter) Write(data []byte) (int, error) { } func TestFailedNotification_RetriesWithoutChangingRanks(t *testing.T) { - for _, shortWrite := range []bool{false, true} { - name := "write-error" - if shortWrite { - name = "zero-byte-write" - } + test := func(name string, shortWrite bool) { t.Run(name, func(t *testing.T) { writer := &recoveringWriter{remaining: 1, shortWrite: shortWrite} m := mgrWith(writer, []string{"test-node-a", "test-node-b"}) @@ -65,6 +61,8 @@ func TestFailedNotification_RetriesWithoutChangingRanks(t *testing.T) { } }) } + test("write-error", false) + test("zero-byte-write", true) } func TestFailedChangedNotification_RetainsDeliveredRanks(t *testing.T) { diff --git a/services/nvpair-manual-nodes/manager_test.go b/services/nvpair-manual-nodes/manager_test.go index 42c14bf0..87de4180 100644 --- a/services/nvpair-manual-nodes/manager_test.go +++ b/services/nvpair-manual-nodes/manager_test.go @@ -299,15 +299,16 @@ func TestNodeAdd_RespondsWithInitialStatusThenDiscoversProbeResult(t *testing.T) func TestNodeAdd_RejectsInvalidParameters(t *testing.T) { m, rw, _ := newTestManager() - m.handleMessage(requestMessageRaw(1, "node/add", json.RawMessage(`"bad"`))) - resp := readCaptureFrame(t, rw) - require.NotNil(t, resp.Error, "malformed params error") - assert.Equal(t, -32602, resp.Error.Code, "malformed params error") - - m.handleMessage(requestMessage(2, "node/add", ManualEntry{})) - resp = readCaptureFrame(t, rw) - require.NotNil(t, resp.Error, "missing address error") - assert.Equal(t, -32602, resp.Error.Code, "missing address error") + test := func(name string, request *Message) { + t.Run(name, func(t *testing.T) { + m.handleMessage(request) + resp := readCaptureFrame(t, rw) + require.NotNil(t, resp.Error) + assert.Equal(t, -32602, resp.Error.Code) + }) + } + test("malformed params", requestMessageRaw(1, "node/add", json.RawMessage(`"bad"`))) + test("missing address", requestMessage(2, "node/add", ManualEntry{})) require.Empty(t, m.listNodes(), "validation errors added nodes") } diff --git a/services/nvpair-node-info/gpu_windows_test.go b/services/nvpair-node-info/gpu_windows_test.go index 05cabfb2..ac59e0a8 100644 --- a/services/nvpair-node-info/gpu_windows_test.go +++ b/services/nvpair-node-info/gpu_windows_test.go @@ -63,19 +63,15 @@ func TestIsVirtualDisplayAdapter_RecognizesVirtualAdapters(t *testing.T) { } func TestLuidUint64_CombinesHighAndLowParts(t *testing.T) { - cases := []struct { - low uint32 - high int32 - want uint64 - }{ - {0, 0, 0}, - {0x54f0, 0, 0x54f0}, - {0x000054f0, 0x00000001, 0x00000001000054f0}, - {0xffffffff, -1, 0xffffffffffffffff}, - } - for _, tc := range cases { - assert.Equal(t, tc.want, luidUint64(tc.low, tc.high), "luidUint64") + test := func(name string, low uint32, high int32, want uint64) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, luidUint64(low, high)) + }) } + test("zero", 0, 0, 0) + test("low word", 0x54f0, 0, 0x54f0) + test("both words", 0x000054f0, 0x00000001, 0x00000001000054f0) + test("signed high word", 0xffffffff, -1, 0xffffffffffffffff) } func TestKeepPhysicalAdapter_FiltersByPhysicalAdapterIdentity(t *testing.T) { @@ -83,21 +79,14 @@ func TestKeepPhysicalAdapter_FiltersByPhysicalAdapterIdentity(t *testing.T) { 0x1000: {}, 0x2000: {}, } - cases := []struct { - name string - luid uint64 - physical map[uint64]struct{} - want bool - }{ - {"nil map keeps all", 0x9999, nil, true}, - {"empty map keeps all", 0x9999, map[uint64]struct{}{}, true}, - {"listed LUID kept", 0x1000, physical, true}, - {"unlisted LUID dropped", 0x9999, physical, false}, - {"second listed LUID kept", 0x2000, physical, true}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, keepPhysicalAdapter(tc.luid, tc.physical), "keepPhysicalAdapter") + test := func(name string, luid uint64, physical map[uint64]struct{}, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, keepPhysicalAdapter(luid, physical)) }) } + test("nil map keeps all", 0x9999, nil, true) + test("empty map keeps all", 0x9999, map[uint64]struct{}{}, true) + test("listed LUID kept", 0x1000, physical, true) + test("unlisted LUID dropped", 0x9999, physical, false) + test("second listed LUID kept", 0x2000, physical, true) } diff --git a/services/nvpair-node-info/stats_darwin_test.go b/services/nvpair-node-info/stats_darwin_test.go index 9cd6b13b..c709c0e2 100644 --- a/services/nvpair-node-info/stats_darwin_test.go +++ b/services/nvpair-node-info/stats_darwin_test.go @@ -19,23 +19,16 @@ func TestDarwinCPUUtilization_ComputesUsageBetweenSamples(t *testing.T) { valid := func(idle, total float64) darwinCPUTimes { return darwinCPUTimes{idle: idle, total: total, valid: true} } - cases := []struct { - name string - prev darwinCPUTimes - cur darwinCPUTimes - want uint32 - }{ - {"busy delta", valid(40, 100), valid(60, 200), 80}, - {"idle delta", valid(40, 100), valid(140, 200), 0}, - {"invalid baseline", darwinCPUTimes{}, valid(60, 200), 0}, - {"counter reset", valid(40, 100), valid(20, 50), 0}, - {"no elapsed ticks", valid(40, 100), valid(40, 100), 0}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, darwinCPUUtilization(c.prev, c.cur), "darwinCPUUtilization()") + test := func(name string, prev, cur darwinCPUTimes, want uint32) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, darwinCPUUtilization(prev, cur)) }) } + test("busy delta", valid(40, 100), valid(60, 200), 80) + test("idle delta", valid(40, 100), valid(140, 200), 0) + test("invalid baseline", darwinCPUTimes{}, valid(60, 200), 0) + test("counter reset", valid(40, 100), valid(20, 50), 0) + test("no elapsed ticks", valid(40, 100), valid(40, 100), 0) } func TestDarwinMemorySnapshot_ReportsInitialSystemUsage(t *testing.T) { diff --git a/services/nvpair-node-info/stats_linux_test.go b/services/nvpair-node-info/stats_linux_test.go index 307cd78d..474258e9 100644 --- a/services/nvpair-node-info/stats_linux_test.go +++ b/services/nvpair-node-info/stats_linux_test.go @@ -16,189 +16,76 @@ import ( // (e.g. picking up a per-core "cpuN" line, or miscounting idle) would make // every Linux node report a wrong or zero utilization. func TestParseProcStat_ExtractsIdleAndTotalCPUTicks(t *testing.T) { - cases := []struct { - name string - in string - wantIdle uint64 - wantTotal uint64 - wantValid bool - }{ - { - name: "typical aggregate line, idle = idle+iowait", - in: "cpu 100 0 50 800 40 0 10 0 0 0\ncpu0 50 0 25 400 20 0 5 0 0 0\n", - wantIdle: 840, // idle 800 + iowait 40 - wantTotal: 1000, - wantValid: true, - }, - { - name: "ignores cpuN lines, only aggregate counts", - in: "cpu0 50 0 25 400 20 0 5 0 0 0\ncpu 10 0 10 70 10 0 0 0 0 0\n", - wantIdle: 80, // 70 + 10 - wantTotal: 100, - wantValid: true, - }, - { - name: "no aggregate cpu line", - in: "intr 12345\nctxt 6789\n", - wantValid: false, - }, - { - name: "too few fields", - in: "cpu 1 2\n", - wantValid: false, - }, - { - name: "all-zero totals are invalid", - in: "cpu 0 0 0 0 0 0 0 0 0 0\n", - wantValid: false, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - got := parseProcStat(c.in) - require.Equal(t, c.wantValid, got.valid, "valid") - if !c.wantValid { + test := func(name, in string, wantIdle, wantTotal uint64, wantValid bool) { + t.Run(name, func(t *testing.T) { + got := parseProcStat(in) + require.Equal(t, wantValid, got.valid) + if !wantValid { return } - require.Equal(t, c.wantIdle, got.idle, "idle/total") - require.Equal(t, c.wantTotal, got.total, "idle/total") + require.Equal(t, wantIdle, got.idle) + require.Equal(t, wantTotal, got.total) }) } + // Idle includes both idle and iowait ticks. + test("typical aggregate line, idle = idle+iowait", "cpu 100 0 50 800 40 0 10 0 0 0\ncpu0 50 0 25 400 20 0 5 0 0 0\n", 840, 1000, true) + test("ignores cpuN lines, only aggregate counts", "cpu0 50 0 25 400 20 0 5 0 0 0\ncpu 10 0 10 70 10 0 0 0 0 0\n", 80, 100, true) + test("no aggregate cpu line", "intr 12345\nctxt 6789\n", 0, 0, false) + test("too few fields", "cpu 1 2\n", 0, 0, false) + test("all-zero totals are invalid", "cpu 0 0 0 0 0 0 0 0 0 0\n", 0, 0, false) } // TestCPUUtilization exercises the delta math and every guard: invalid // samples, counter resets, zero elapsed time, and rounding. func TestCPUUtilization_ComputesUsageBetweenSamples(t *testing.T) { - cases := []struct { - name string - prev cpuTimes - cur cpuTimes - want uint32 - }{ - { - name: "50 percent busy", - prev: cpuTimes{idle: 100, total: 200, valid: true}, - cur: cpuTimes{idle: 200, total: 400, valid: true}, - want: 50, - }, - { - name: "fully busy", - prev: cpuTimes{idle: 100, total: 200, valid: true}, - cur: cpuTimes{idle: 100, total: 300, valid: true}, - want: 100, - }, - { - name: "fully idle", - prev: cpuTimes{idle: 100, total: 200, valid: true}, - cur: cpuTimes{idle: 200, total: 300, valid: true}, - want: 0, - }, - { - name: "rounds half up", - prev: cpuTimes{idle: 0, total: 0, valid: true}, - cur: cpuTimes{idle: 425, total: 1000, valid: true}, // 57.5% busy - want: 58, - }, - { - name: "invalid prev -> 0", - prev: cpuTimes{}, - cur: cpuTimes{idle: 1, total: 2, valid: true}, - want: 0, - }, - { - name: "counter reset (total went backwards) -> 0", - prev: cpuTimes{idle: 100, total: 500, valid: true}, - cur: cpuTimes{idle: 50, total: 200, valid: true}, - want: 0, - }, - { - name: "no elapsed jiffies -> 0", - prev: cpuTimes{idle: 100, total: 200, valid: true}, - cur: cpuTimes{idle: 100, total: 200, valid: true}, - want: 0, - }, + valid := func(idle, total uint64) cpuTimes { + return cpuTimes{idle: idle, total: total, valid: true} } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, cpuUtilization(c.prev, c.cur), "cpuUtilization()") + test := func(name string, prev, cur cpuTimes, want uint32) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, cpuUtilization(prev, cur)) }) } + test("50 percent busy", valid(100, 200), valid(200, 400), 50) + test("fully busy", valid(100, 200), valid(100, 300), 100) + test("fully idle", valid(100, 200), valid(200, 300), 0) + test("rounds half up", valid(0, 0), valid(425, 1000), 58) // 57.5% busy + test("invalid prev -> 0", cpuTimes{}, valid(1, 2), 0) + test("counter reset (total went backwards) -> 0", valid(100, 500), valid(50, 200), 0) + test("no elapsed jiffies -> 0", valid(100, 200), valid(100, 200), 0) } // TestInitialMemorySnapshot verifies that startup publishes a usable memory // sample before the first ticker event while preserving omission semantics // when /proc/meminfo cannot be read. func TestMemorySnapshot_ReportsInitialSystemUsage(t *testing.T) { - cases := []struct { - name string - readUsed func() (uint64, bool) - wantUsed uint64 - }{ - { - name: "successful startup read", - readUsed: func() (uint64, bool) { - return 48 << 30, true - }, - wantUsed: 48 << 30, - }, - { - name: "failed startup read remains unknown", - readUsed: func() (uint64, bool) { - return 123, false - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - snap := initialMemorySnapshot(c.readUsed) - require.Equal(t, c.wantUsed, snap.MemUsedBytes, "MemUsedBytes") + test := func(name string, used uint64, ok bool, wantUsed uint64) { + t.Run(name, func(t *testing.T) { + snap := initialMemorySnapshot(func() (uint64, bool) { return used, ok }) + require.Equal(t, wantUsed, snap.MemUsedBytes) }) } + test("successful startup read", 48<<30, true, 48<<30) + test("failed startup read remains unknown", 123, false, 0) } // TestParseMeminfoUsed pins the MemTotal-MemAvailable computation, the // MemFree fallback for pre-3.14 kernels, the kB->bytes conversion, and the // failure cases (missing total, underflow). func TestParseMeminfoUsed_ComputesUsedMemoryFromAvailableMemory(t *testing.T) { - cases := []struct { - name string - in string - wantUsed uint64 - wantOK bool - }{ - { - name: "MemAvailable present", - in: "MemTotal: 1000 kB\nMemFree: 200 kB\nMemAvailable: 400 kB\n", - wantUsed: 600 * 1024, // (1000 - 400) kB - wantOK: true, - }, - { - name: "MemAvailable absent, falls back to MemFree", - in: "MemTotal: 1000 kB\nMemFree: 200 kB\nBuffers: 50 kB\n", - wantUsed: 800 * 1024, // (1000 - 200) kB - wantOK: true, - }, - { - name: "missing MemTotal", - in: "MemFree: 200 kB\nMemAvailable: 400 kB\n", - wantOK: false, - }, - { - name: "available exceeds total and no usable free -> fail", - in: "MemTotal: 100 kB\nMemAvailable: 200 kB\n", - wantOK: false, - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - used, ok := parseMeminfoUsed(c.in) - require.Equal(t, c.wantOK, ok, "ok (%v)", ok) + test := func(name, in string, wantUsed uint64, wantOK bool) { + t.Run(name, func(t *testing.T) { + used, ok := parseMeminfoUsed(in) + require.Equal(t, wantOK, ok) if ok { - require.Equal(t, c.wantUsed, used) + require.Equal(t, wantUsed, used) } }) } + test("MemAvailable present", "MemTotal: 1000 kB\nMemFree: 200 kB\nMemAvailable: 400 kB\n", 600*1024, true) // (1000 - 400) kB + test("MemAvailable absent, falls back to MemFree", "MemTotal: 1000 kB\nMemFree: 200 kB\nBuffers: 50 kB\n", 800*1024, true) // (1000 - 200) kB + test("missing MemTotal", "MemFree: 200 kB\nMemAvailable: 400 kB\n", 0, false) + test("available exceeds total and no usable free -> fail", "MemTotal: 100 kB\nMemAvailable: 200 kB\n", 0, false) } // TestParseNvidiaStatic pins the static enumeration parse: name + total VRAM @@ -325,19 +212,16 @@ func TestParseNvidiaDynamic_ExtractsGPUUsage(t *testing.T) { // TestIsNvidiaSmiNA pins recognition of nvidia-smi not-applicable sentinels. func TestIsNvidiaSmiNA_RecognizesUnavailableValues(t *testing.T) { - cases := []struct { - in string - want bool - }{ - {"[N/A]", true}, - {"N/A", true}, - {"[Not Supported]", true}, - {"Not Supported", true}, - {"8192", false}, - {"notanumber", false}, - {"", false}, - } - for _, c := range cases { - require.Equal(t, c.want, isNvidiaSmiNA(c.in), "isNvidiaSmiNA(%q)", c.in) + test := func(name, in string, want bool) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, isNvidiaSmiNA(in)) + }) } + test("bracketed N/A", "[N/A]", true) + test("N/A", "N/A", true) + test("bracketed unsupported", "[Not Supported]", true) + test("unsupported", "Not Supported", true) + test("number", "8192", false) + test("malformed number", "notanumber", false) + test("empty", "", false) } diff --git a/services/nvpair-node-info/stats_test.go b/services/nvpair-node-info/stats_test.go index 02929ece..83f35333 100644 --- a/services/nvpair-node-info/stats_test.go +++ b/services/nvpair-node-info/stats_test.go @@ -22,48 +22,22 @@ import ( // a few malformed variants we must reject, and the edge case of a // trailing underscore with no engine type. func TestParseEngineInstance_ExtractsEngineAndNode(t *testing.T) { - cases := []struct { - name string - in string - wantLuid string - wantType string - wantOK bool - }{ - { - name: "typical 3D engine", - in: "pid_1234_luid_0x00000000_0x000054f0_phys_0_eng_0_engtype_3D", - wantLuid: "luid_0x00000000_0x000054f0_phys_0", - wantType: "3d", - wantOK: true, - }, - { - name: "compute variant", - in: "pid_99_luid_0x00000000_0x0000abcd_phys_0_eng_2_engtype_Compute_0", - wantLuid: "luid_0x00000000_0x0000abcd_phys_0", - wantType: "compute_0", - wantOK: true, - }, - { - name: "mixed-case hex normalizes to lower", - in: "pid_1_luid_0x00000000_0x000054F0_phys_0_eng_0_engtype_3D", - wantLuid: "luid_0x00000000_0x000054f0_phys_0", - wantType: "3d", - wantOK: true, - }, - {"missing luid prefix", "pid_1_eng_0_engtype_3D", "", "", false}, - {"missing eng segment", "pid_1_luid_0x0_0x0_phys_0_engtype_3D", "", "", false}, - {"missing engtype marker", "pid_1_luid_0x0_0x0_phys_0_eng_0", "", "", false}, - {"empty engtype value", "pid_1_luid_0x0_0x0_phys_0_eng_0_engtype_", "", "", false}, - {"unrelated garbage", "completely unrelated string", "", "", false}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - luid, et, ok := parseEngineInstance(c.in) - require.Equal(t, c.wantOK, ok, "parseEngineInstance (%v, %v, %v)", luid, et, ok) - require.Equal(t, c.wantLuid, luid, "parseEngineInstance (%v, %v, %v)", luid, et, ok) - require.Equal(t, c.wantType, et, "parseEngineInstance (%v, %v, %v)", luid, et, ok) + test := func(name, in, wantLuid, wantType string, wantOK bool) { + t.Run(name, func(t *testing.T) { + luid, et, ok := parseEngineInstance(in) + require.Equal(t, wantOK, ok) + require.Equal(t, wantLuid, luid) + require.Equal(t, wantType, et) }) } + test("typical 3D engine", "pid_1234_luid_0x00000000_0x000054f0_phys_0_eng_0_engtype_3D", "luid_0x00000000_0x000054f0_phys_0", "3d", true) + test("compute variant", "pid_99_luid_0x00000000_0x0000abcd_phys_0_eng_2_engtype_Compute_0", "luid_0x00000000_0x0000abcd_phys_0", "compute_0", true) + test("mixed-case hex normalizes to lower", "pid_1_luid_0x00000000_0x000054F0_phys_0_eng_0_engtype_3D", "luid_0x00000000_0x000054f0_phys_0", "3d", true) + test("missing luid prefix", "pid_1_eng_0_engtype_3D", "", "", false) + test("missing eng segment", "pid_1_luid_0x0_0x0_phys_0_engtype_3D", "", "", false) + test("missing engtype marker", "pid_1_luid_0x0_0x0_phys_0_eng_0", "", "", false) + test("empty engtype value", "pid_1_luid_0x0_0x0_phys_0_eng_0_engtype_", "", "", false) + test("unrelated garbage", "completely unrelated string", "", "", false) } // TestAggregateUtilization exercises Task Manager's three-step algorithm: @@ -186,25 +160,15 @@ func TestApplyGPUStats_PublishesIdleAndPreSampleFields(t *testing.T) { func TestBuildResponse_ReportsTelemetryAgeAndFreshness(t *testing.T) { now := time.Unix(1_700_000_000, 500_000_000) - cases := []struct { - name string - sampledAt time.Time - wantValid bool - wantAge int64 - }{ - {name: "no sample", wantValid: false, wantAge: 0}, - {name: "valid idle sample", sampledAt: now.Add(-137 * time.Millisecond), wantValid: true, wantAge: 137}, - {name: "future sample clamps age", sampledAt: now.Add(time.Millisecond), wantValid: true, wantAge: 0}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { + test := func(name string, sampledAt time.Time, wantValid bool, wantAge int64) { + t.Run(name, func(t *testing.T) { body := buildResponseAt( []GPUInfo{{Name: "GPU 0", statsKey: "gpu-a"}}, nil, 0, statsSnapshot{ GPU: map[string]gpuStat{"gpu-a": {UtilizationPct: 0}}, - GPUSampledAt: c.sampledAt, + GPUSampledAt: sampledAt, }, "", nil, @@ -212,8 +176,8 @@ func TestBuildResponse_ReportsTelemetryAgeAndFreshness(t *testing.T) { ) var typed NodeInfoResponse require.NoError(t, json.Unmarshal(body, &typed), "decode response") - require.Equal(t, c.wantValid, typed.TelemetryValid, "telemetry = valid") - require.Equal(t, c.wantAge, typed.MSSince, "telemetry = valid") + require.Equal(t, wantValid, typed.TelemetryValid) + require.Equal(t, wantAge, typed.MSSince) var raw map[string]any require.NoError(t, json.Unmarshal(body, &raw), "decode raw response") @@ -221,6 +185,9 @@ func TestBuildResponse_ReportsTelemetryAgeAndFreshness(t *testing.T) { require.Contains(t, raw, "msSince", "msSince missing from response") }) } + test("no sample", time.Time{}, false, 0) + test("valid idle sample", now.Add(-137*time.Millisecond), true, 137) + test("future sample clamps age", now.Add(time.Millisecond), true, 0) } // buildResponseDecode is a test helper that marshals through the real @@ -273,30 +240,12 @@ func TestBuildResponseUnifiedMemory_UsesSystemSnapshot(t *testing.T) { statsKey: "GPU-spark", usesSystemMemoryUsage: true, }} - cases := []struct { - name string - gpuStats map[string]gpuStat - wantUtil uint32 - }{ - { - name: "without dynamic nvidia-smi row", - gpuStats: nil, - }, - { - name: "with dynamic utilization row", - gpuStats: map[string]gpuStat{ - "GPU-spark": {VRAMUsed: 1 << 30, UtilizationPct: 42}, - }, - wantUtil: 42, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - snap := statsSnapshot{GPU: c.gpuStats, MemUsedBytes: sysMemUsed} + test := func(name string, gpuStats map[string]gpuStat, wantUtil uint32) { + t.Run(name, func(t *testing.T) { + snap := statsSnapshot{GPU: gpuStats, MemUsedBytes: sysMemUsed} typed, raw := buildResponseDecode(t, static, nil, 0, snap) - assert.Equal(t, sysMemUsed, typed.GPUs[0].VramUsedBytes, "VramUsedBytes") - assert.Equal(t, c.wantUtil, typed.GPUs[0].UtilizationPercent, "UtilizationPercent") + assert.Equal(t, sysMemUsed, typed.GPUs[0].VramUsedBytes) + assert.Equal(t, wantUtil, typed.GPUs[0].UtilizationPercent) gpus, ok := raw["GPUs"].([]any) require.True(t, ok, "unexpected GPUs payload") @@ -306,6 +255,8 @@ func TestBuildResponseUnifiedMemory_UsesSystemSnapshot(t *testing.T) { assert.Contains(t, obj, "vram_used_bytes", "vram_used_bytes should be present for unified memory") }) } + test("without dynamic nvidia-smi row", nil, 0) + test("with dynamic utilization row", map[string]gpuStat{"GPU-spark": {VRAMUsed: 1 << 30, UtilizationPct: 42}}, 42) } // Apple Silicon shares physical memory but ioreg reports the GPU-specific diff --git a/services/nvpair-node-info/stats_windows_test.go b/services/nvpair-node-info/stats_windows_test.go index a8e687de..7217d850 100644 --- a/services/nvpair-node-info/stats_windows_test.go +++ b/services/nvpair-node-info/stats_windows_test.go @@ -22,20 +22,13 @@ import ( // bit-reinterpretation for the signed HighPart rather than numeric sign- // extension. func TestLuidKey_FormatsAdapterIdentity(t *testing.T) { - cases := []struct { - name string - low uint32 - high int32 - want string - }{ - {"typical", 0x000054F0, 0x00000000, "luid_0x00000000_0x000054f0_phys_0"}, - {"high-bit-set", 0xDEADBEEF, -1, "luid_0xffffffff_0xdeadbeef_phys_0"}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - require.Equal(t, c.want, luidKey(c.low, c.high), "luidKey") + test := func(name string, low uint32, high int32, want string) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, luidKey(low, high)) }) } + test("typical", 0x000054F0, 0x00000000, "luid_0x00000000_0x000054f0_phys_0") + test("high-bit-set", 0xDEADBEEF, -1, "luid_0xffffffff_0xdeadbeef_phys_0") } // TestPDHFmtCounterValueSize pins the scalar pdhFmtCounterValue to the diff --git a/services/nvpair-node-scanner/ghost_node_test.go b/services/nvpair-node-scanner/ghost_node_test.go index 44acc2c7..8b3753cb 100644 --- a/services/nvpair-node-scanner/ghost_node_test.go +++ b/services/nvpair-node-scanner/ghost_node_test.go @@ -121,34 +121,16 @@ func TestReachable_FallsBackWithoutIdentity(t *testing.T) { tcpPort := openPort(t) niHost, niPort := nodeInfoServer(t, "") // answers, but reports no identity - cases := []struct { - name string - txt []string - }{ - { - name: "no ni advertised", - txt: []string{"v=1", "uuid=some-uuid", "ip=127.0.0.1", fmt.Sprintf("em=%d", tcpPort)}, - }, - { - name: "node-info not answering", - txt: []string{"v=1", "uuid=some-uuid", "ip=127.0.0.1", - fmt.Sprintf("ni=%d", closedPort(t)), fmt.Sprintf("em=%d", tcpPort)}, - }, - { - name: "node-info reports no hostUuid", - txt: []string{"v=1", "uuid=some-uuid", "ip=" + niHost, - fmt.Sprintf("ni=%d", niPort), fmt.Sprintf("em=%d", tcpPort)}, - }, - { - name: "record carries no uuid to compare", - txt: []string{"v=1", "ip=127.0.0.1", fmt.Sprintf("ni=%d", niPort), fmt.Sprintf("em=%d", tcpPort)}, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - require.True(t, probeDaemon().reachable(rawNode(tc.txt...)), "with identity unavailable the probe must fall back to the TCP sweep") + test := func(name string, txt ...string) { + t.Run(name, func(t *testing.T) { + require.True(t, probeDaemon().reachable(rawNode(txt...)), "with identity unavailable the probe must fall back to the TCP sweep") }) } + deadPort := closedPort(t) + test("no ni advertised", "v=1", "uuid=some-uuid", "ip=127.0.0.1", fmt.Sprintf("em=%d", tcpPort)) + test("node-info not answering", "v=1", "uuid=some-uuid", "ip=127.0.0.1", fmt.Sprintf("ni=%d", deadPort), fmt.Sprintf("em=%d", tcpPort)) + test("node-info reports no hostUuid", "v=1", "uuid=some-uuid", "ip="+niHost, fmt.Sprintf("ni=%d", niPort), fmt.Sprintf("em=%d", tcpPort)) + test("record carries no uuid to compare", "v=1", "ip=127.0.0.1", fmt.Sprintf("ni=%d", niPort), fmt.Sprintf("em=%d", tcpPort)) } // TestReachableEvictsWhenNothingAnswers keeps the base case honest: a node that diff --git a/services/nvpair-node-scanner/telemetry_test.go b/services/nvpair-node-scanner/telemetry_test.go index c9991a0c..31261ab6 100644 --- a/services/nvpair-node-scanner/telemetry_test.go +++ b/services/nvpair-node-scanner/telemetry_test.go @@ -144,20 +144,17 @@ func TestTelemetryIntervalForNode_IsStableAndBounded(t *testing.T) { } func TestTelemetryRetryDelay_BoundsBackoff(t *testing.T) { - cases := []struct { - failures int - want time.Duration - }{ - {0, 2 * time.Second}, - {1, 4 * time.Second}, - {2, 8 * time.Second}, - {3, 16 * time.Second}, - {4, 30 * time.Second}, - {100, 30 * time.Second}, - } - for _, test := range cases { - assert.Equal(t, test.want, telemetryRetryDelay(test.failures), "telemetryRetryDelay(%d)", test.failures) + test := func(name string, failures int, want time.Duration) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, telemetryRetryDelay(failures)) + }) } + test("initial delay", 0, 2*time.Second) + test("one failure", 1, 4*time.Second) + test("two failures", 2, 8*time.Second) + test("three failures", 3, 16*time.Second) + test("maximum delay", 4, 30*time.Second) + test("delay stays capped", 100, 30*time.Second) } func TestTelemetryRetryGate_BacksOffAndResets(t *testing.T) { @@ -185,11 +182,7 @@ func TestTelemetryRetryGate_BacksOffAndResets(t *testing.T) { func TestTelemetryRetryGateChangedTarget_WaitsForActiveAttempt(t *testing.T) { oldTarget := telemetryTargetKey([]string{"192.0.2.1"}, 14318) - changedTargets := map[string]string{ - "hosts": telemetryTargetKey([]string{"10.0.0.1"}, 14318), - "port": telemetryTargetKey([]string{"192.0.2.1"}, 14319), - } - for name, changedTarget := range changedTargets { + test := func(name, changedTarget string) { t.Run(name, func(t *testing.T) { var gate telemetryRetryGate now := time.Unix(1_500, 0) @@ -203,6 +196,8 @@ func TestTelemetryRetryGateChangedTarget_WaitsForActiveAttempt(t *testing.T) { require.True(t, ok, "old endpoint failure backed off the changed endpoint") }) } + test("hosts", telemetryTargetKey([]string{"10.0.0.1"}, 14318)) + test("port", telemetryTargetKey([]string{"192.0.2.1"}, 14319)) } func TestTelemetryRetryGateRemoveRediscover_PreservesActiveClaim(t *testing.T) { diff --git a/services/nvpair-node-scanner/tls_test.go b/services/nvpair-node-scanner/tls_test.go index e3f1a093..9be1269b 100644 --- a/services/nvpair-node-scanner/tls_test.go +++ b/services/nvpair-node-scanner/tls_test.go @@ -19,27 +19,21 @@ import ( ) func TestTLSClientOptions_ValidatesConfiguration(t *testing.T) { - cases := []struct { - name string - o tlsClientOptions - ok bool - }{ - {"none", tlsClientOptions{}, true}, - {"ca only", tlsClientOptions{CABundlePath: "ca.pem"}, true}, - {"cert+key", tlsClientOptions{CertPath: "c", KeyPath: "k"}, true}, - {"cert without key", tlsClientOptions{CertPath: "c"}, false}, - {"key without cert", tlsClientOptions{KeyPath: "k"}, false}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - err := tc.o.validate() - if tc.ok { - require.NoError(t, err, "validate()") + test := func(name string, options tlsClientOptions, wantOK bool) { + t.Run(name, func(t *testing.T) { + err := options.validate() + if wantOK { + require.NoError(t, err) } else { - require.Error(t, err, "validate()") + require.Error(t, err) } }) } + test("none", tlsClientOptions{}, true) + test("ca only", tlsClientOptions{CABundlePath: "ca.pem"}, true) + test("cert+key", tlsClientOptions{CertPath: "c", KeyPath: "k"}, true) + test("cert without key", tlsClientOptions{CertPath: "c"}, false) + test("key without cert", tlsClientOptions{KeyPath: "k"}, false) } // TestBuildTLSClientUnconfiguredIsNil is the dormant-by-default guarantee: with diff --git a/services/nvpair-node-settings/manager_test.go b/services/nvpair-node-settings/manager_test.go index 3ebf13af..090d34df 100644 --- a/services/nvpair-node-settings/manager_test.go +++ b/services/nvpair-node-settings/manager_test.go @@ -273,14 +273,15 @@ func TestConnectionEndpoints_AreNotRequestMethods(t *testing.T) { func TestLegacySettingsMethods_ReturnMethodNotFound(t *testing.T) { m, rw, _ := newTestManager(t) - for i, method := range []string{ - "settings/get-auto-join-invites", - "settings/set-auto-join-invites", - "settings/get-cluster-secret", - "settings/set-cluster-secret", - } { - callExpectError(t, m, rw, i+1, method, map[string]any{"value": "ignored"}, -32601) + test := func(name string, id int, method string) { + t.Run(name, func(t *testing.T) { + callExpectError(t, m, rw, id, method, map[string]any{"value": "ignored"}, -32601) + }) } + test("get auto-join invites", 1, "settings/get-auto-join-invites") + test("set auto-join invites", 2, "settings/set-auto-join-invites") + test("get cluster secret", 3, "settings/get-cluster-secret") + test("set cluster secret", 4, "settings/set-cluster-secret") } // TestSetClusterIDEmitsConnectionIdentityNotification verifies the @@ -595,40 +596,19 @@ func TestLoadMalformedFile_RenamesAsideAndStartsWithDefaults(t *testing.T) { func TestTypeValidation_RejectsWrongTypeAndMissingValue(t *testing.T) { m, rw, _ := newTestManager(t) - // Wrong type for a bool field. - m.handleMessage(requestMessageRaw(1, "settings/set-cluster-auto-sync", - json.RawMessage(`{"value":"not-a-bool"}`))) - resp := readCaptureFrame(t, rw) - require.NotNil(t, resp.Error, "wrong-type bool error") - assert.Equal(t, -32602, resp.Error.Code, "wrong-type bool error") - - // Missing `value` field. - m.handleMessage(requestMessageRaw(2, "settings/set-cluster-auto-sync", - json.RawMessage(`{}`))) - resp = readCaptureFrame(t, rw) - require.NotNil(t, resp.Error, "missing-value error") - assert.Equal(t, -32602, resp.Error.Code, "missing-value error") - - // Wrong type for cluster-id (number where string expected). - m.handleMessage(requestMessageRaw(3, "settings/set-cluster-id", - json.RawMessage(`{"value":123}`))) - resp = readCaptureFrame(t, rw) - require.NotNil(t, resp.Error, "wrong-type cluster-id error") - assert.Equal(t, -32602, resp.Error.Code, "wrong-type cluster-id error") - - // Wrong type for cluster-friendly-name (bool where string expected). - m.handleMessage(requestMessageRaw(4, "settings/set-cluster-friendly-name", - json.RawMessage(`{"value":true}`))) - resp = readCaptureFrame(t, rw) - require.NotNil(t, resp.Error, "wrong-type cluster-friendly-name error") - assert.Equal(t, -32602, resp.Error.Code, "wrong-type cluster-friendly-name error") - - // Wrong type for the force-ports bool (a string). - m.handleMessage(requestMessageRaw(5, "settings/set-force-ports", - json.RawMessage(`{"value":"on"}`))) - resp = readCaptureFrame(t, rw) - require.NotNil(t, resp.Error, "wrong-type force-ports error") - assert.Equal(t, -32602, resp.Error.Code, "wrong-type force-ports error") + test := func(name string, id int, method, params string) { + t.Run(name, func(t *testing.T) { + m.handleMessage(requestMessageRaw(id, method, json.RawMessage(params))) + resp := readCaptureFrame(t, rw) + require.NotNil(t, resp.Error) + assert.Equal(t, -32602, resp.Error.Code) + }) + } + test("wrong-type bool", 1, "settings/set-cluster-auto-sync", `{"value":"not-a-bool"}`) + test("missing value", 2, "settings/set-cluster-auto-sync", `{}`) + test("wrong-type cluster ID", 3, "settings/set-cluster-id", `{"value":123}`) + test("wrong-type cluster friendly name", 4, "settings/set-cluster-friendly-name", `{"value":true}`) + test("wrong-type force ports", 5, "settings/set-force-ports", `{"value":"on"}`) } func TestUnknownMethod_ReturnsMethodNotFound(t *testing.T) { diff --git a/services/nvpair-proxy/alias_test.go b/services/nvpair-proxy/alias_test.go index e3ce8e94..a658272d 100644 --- a/services/nvpair-proxy/alias_test.go +++ b/services/nvpair-proxy/alias_test.go @@ -20,28 +20,30 @@ import ( ) func TestSetLoopbackAlias_RejectsUnsafeAddresses(t *testing.T) { - for _, tc := range []struct { - address string - wantErr bool - wantAddr string - }{ - {"127.0.0.1:11433", false, "127.0.0.1:11433"}, - {"127.0.0.2:11433", false, "127.0.0.2:11433"}, - {"localhost:11433", false, "127.0.0.1:11433"}, - {"[::1]:11433", false, "[::1]:11433"}, - {"0.0.0.0:11433", true, ""}, - {"192.168.1.20:11433", true, ""}, - {"localhost:11434", true, ""}, - {"localhost:0", true, ""}, - {"not-an-address", true, ""}, - } { - p := newTestProxy(ollamaOnlyProfile(t), NewCodec(rwNop{}), NewDiscovery(), 11434) - if err := p.soleFacade().setLoopbackAlias(tc.address); (err != nil) != tc.wantErr { - assert.Fail(t, fmt.Sprintf("setLoopbackAlias(%q) error = %v, wantErr %v", tc.address, err, tc.wantErr)) - } else if err == nil { - assert.Equal(t, tc.wantAddr, p.soleFacade().aliasAddr, "setLoopbackAlias") - } + accept := func(name, address, wantAddr string) { + t.Run(name, func(t *testing.T) { + p := newTestProxy(ollamaOnlyProfile(t), NewCodec(rwNop{}), NewDiscovery(), 11434) + require.NoError(t, p.soleFacade().setLoopbackAlias(address)) + + assert.Equal(t, wantAddr, p.soleFacade().aliasAddr) + + }) + } + reject := func(name, address string) { + t.Run(name, func(t *testing.T) { + p := newTestProxy(ollamaOnlyProfile(t), NewCodec(rwNop{}), NewDiscovery(), 11434) + assert.Error(t, p.soleFacade().setLoopbackAlias(address)) + }) } + accept("primary loopback address", "127.0.0.1:11433", "127.0.0.1:11433") + accept("alternate loopback address", "127.0.0.2:11433", "127.0.0.2:11433") + accept("localhost resolves to loopback", "localhost:11433", "127.0.0.1:11433") + accept("IPv6 loopback", "[::1]:11433", "[::1]:11433") + reject("wildcard address", "0.0.0.0:11433") + reject("LAN address", "192.168.1.20:11433") + reject("primary listener port", "localhost:11434") + reject("zero port", "localhost:0") + reject("malformed address", "not-an-address") } func TestLoopbackAlias_UsesPrimaryRouterAndSurvivesPrimaryRebind(t *testing.T) { diff --git a/services/nvpair-proxy/engines_test.go b/services/nvpair-proxy/engines_test.go index c59d3b8e..5c855b7e 100644 --- a/services/nvpair-proxy/engines_test.go +++ b/services/nvpair-proxy/engines_test.go @@ -35,14 +35,16 @@ func TestLlamaCPPProfile_DeclaresChatRouteAndReservedEnginePort(t *testing.T) { func TestLlamaCPPModelList_UsesNativeInventoryForBothPublicRoutes(t *testing.T) { profile, ok := profileFor("llamacpp") require.True(t, ok, "llamacpp profile missing") - for _, path := range []string{"/models", "/v1/models"} { - t.Run(path, func(t *testing.T) { + test := func(name, path string) { + t.Run(name, func(t *testing.T) { route, ok := profile.routeFor(http.MethodGet, path) require.True(t, ok, "model list route is not classified") require.Equal(t, roleModelListOpenAIGET, route.Role, "model list route") require.Equal(t, "/models", route.upstreamPath(), "upstream model list path") }) } + test("native model list", "/models") + test("OpenAI model list", "/v1/models") } // Routes is a classifier, not an allowlist. handlePlain forwards every @@ -123,32 +125,28 @@ func TestModelNames_FollowEngineNormalizationRules(t *testing.T) { ollama, _ := profileFor("ollama") lmstudio, _ := profileFor("lmstudio") - for _, tc := range []struct { - profile engineProfile - in string - want string - }{ - {ollama, "llama3", "llama3:latest"}, - {ollama, "llama3:latest", "llama3:latest"}, - {ollama, "llama3:8b", "llama3:8b"}, - {ollama, "registry.example/llama3", "registry.example/llama3:latest"}, - {ollama, "llama3@sha256:abc", "llama3@sha256:abc"}, - {ollama, "library/qwen3:latest", "qwen3:latest"}, - {ollama, "registry.ollama.ai/library/qwen3:latest", "qwen3:latest"}, - {ollama, "https://registry.ollama.ai/library/qwen3", "qwen3:latest"}, - {ollama, "Qwen3", "qwen3:latest"}, - {ollama, "registry.ollama.ai/qwen3", "registry.ollama.ai/qwen3:latest"}, - {ollama, "hf.co/Org/Model:Q4_K_M", "hf.co/org/model:q4_k_m"}, - {ollama, "localhost:5000/ns/m", "localhost:5000/ns/m:latest"}, - {ollama, "", ""}, - - {lmstudio, "qwen3-8b", "qwen3-8b"}, - {lmstudio, "qwen3-8b:latest", "qwen3-8b:latest"}, - {lmstudio, "Qwen3-8B", "Qwen3-8B"}, - {lmstudio, "", ""}, - } { - assert.Equal(t, tc.want, tc.profile.normalizeModel(tc.in), "%s normalizeModel(%q)", tc.profile.Name, tc.in) + test := func(name string, profile engineProfile, in, want string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, profile.normalizeModel(in)) + }) } + test("Ollama default tag", ollama, "llama3", "llama3:latest") + test("Ollama explicit default tag", ollama, "llama3:latest", "llama3:latest") + test("Ollama explicit tag", ollama, "llama3:8b", "llama3:8b") + test("Ollama registry prefix", ollama, "registry.example/llama3", "registry.example/llama3:latest") + test("Ollama digest", ollama, "llama3@sha256:abc", "llama3@sha256:abc") + test("Ollama library namespace", ollama, "library/qwen3:latest", "qwen3:latest") + test("Ollama default registry and library", ollama, "registry.ollama.ai/library/qwen3:latest", "qwen3:latest") + test("Ollama registry URL", ollama, "https://registry.ollama.ai/library/qwen3", "qwen3:latest") + test("Ollama case normalization", ollama, "Qwen3", "qwen3:latest") + test("Ollama explicit registry without namespace", ollama, "registry.ollama.ai/qwen3", "registry.ollama.ai/qwen3:latest") + test("Ollama custom registry case normalization", ollama, "hf.co/Org/Model:Q4_K_M", "hf.co/org/model:q4_k_m") + test("Ollama registry port", ollama, "localhost:5000/ns/m", "localhost:5000/ns/m:latest") + test("Ollama empty name", ollama, "", "") + test("LM Studio untagged name", lmstudio, "qwen3-8b", "qwen3-8b") + test("LM Studio exact tag", lmstudio, "qwen3-8b:latest", "qwen3-8b:latest") + test("LM Studio preserves case", lmstudio, "Qwen3-8B", "Qwen3-8B") + test("LM Studio empty name", lmstudio, "", "") } func TestNodeAdvertisesModel_UsesEngineSpecificModelNames(t *testing.T) { diff --git a/services/nvpair-proxy/ingress_test.go b/services/nvpair-proxy/ingress_test.go index e3afba11..30368792 100644 --- a/services/nvpair-proxy/ingress_test.go +++ b/services/nvpair-proxy/ingress_test.go @@ -97,19 +97,17 @@ func TestHandleClusterIngress_RejectsRequestsWithoutClusterMembership(t *testing } func TestIsLoopbackRemote_DistinguishesLoopbackAndLANClients(t *testing.T) { - for _, c := range []struct { - addr string - want bool - }{ - {"127.0.0.1:5000", true}, - {"[::1]:5000", true}, - {"192.168.1.10:5000", false}, - {"10.0.0.5:80", false}, - {"", false}, - {"garbage", false}, - } { - assert.Equal(t, c.want, isLoopbackRemote(c.addr), "isLoopbackRemote(%q)", c.addr) + test := func(name, addr string, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, isLoopbackRemote(addr)) + }) } + test("IPv4 loopback", "127.0.0.1:5000", true) + test("IPv6 loopback", "[::1]:5000", true) + test("LAN address", "192.168.1.10:5000", false) + test("private address", "10.0.0.5:80", false) + test("empty address", "", false) + test("malformed address", "garbage", false) } func TestLocalReverseProxy_UsesSharedPlainTransport(t *testing.T) { diff --git a/services/nvpair-proxy/retry_test.go b/services/nvpair-proxy/retry_test.go index b3847045..daddd31b 100644 --- a/services/nvpair-proxy/retry_test.go +++ b/services/nvpair-proxy/retry_test.go @@ -97,22 +97,18 @@ func TestBackoffFor_BoundsRetryDelay(t *testing.T) { // Indexed by dispatches already made, so the wait after the first dispatch // is the first entry. A deadline an hour out keeps the remaining-time clamp // out of it. Jitter is up to ±25%, so assert a band. - for _, tc := range []struct { - dispatches int - want time.Duration - }{ - {dispatches: 1, want: time.Second}, - {dispatches: 2, want: 2 * time.Second}, - {dispatches: 3, want: 4 * time.Second}, - {dispatches: 4, want: 8 * time.Second}, - {dispatches: 9, want: 8 * time.Second}, // clamped to the last entry - } { - got := backoffFor(tc.dispatches, time.Hour) - lo := tc.want - tc.want/4 - hi := tc.want + tc.want/4 - assert.GreaterOrEqual(t, got, lo, "backoffFor") - assert.LessOrEqual(t, got, hi, "backoffFor") + test := func(name string, dispatches int, want time.Duration) { + t.Run(name, func(t *testing.T) { + got := backoffFor(dispatches, time.Hour) + assert.GreaterOrEqual(t, got, want-want/4) + assert.LessOrEqual(t, got, want+want/4) + }) } + test("first dispatch", 1, time.Second) + test("second dispatch", 2, 2*time.Second) + test("third dispatch", 3, 4*time.Second) + test("fourth dispatch", 4, 8*time.Second) + test("clamped to final entry", 9, 8*time.Second) } // TestBackoffFor_NeverOutlastsTheDeadline: a wait longer than the time left diff --git a/services/nvpair-proxy/twofacade_test.go b/services/nvpair-proxy/twofacade_test.go index 2589e4ae..71764fe8 100644 --- a/services/nvpair-proxy/twofacade_test.go +++ b/services/nvpair-proxy/twofacade_test.go @@ -346,31 +346,26 @@ func TestEnableFacade_RejectsUnsafeAliasAddresses(t *testing.T) { t.Skip("needs one alias-capable and one alias-incapable engine") } - for _, tc := range []struct { - name string - engine string - alias string - }{ - {"a LAN address", aliasEngine.Name, "192.168.1.20:11433"}, - {"the wildcard address", aliasEngine.Name, "0.0.0.0:11433"}, - {"an engine with no inherited host variable", plainEngine.Name, "127.0.0.1:11433"}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name, engine, alias string) { + t.Run(name, func(t *testing.T) { p := NewProxy(NewCodec(rwNop{})) p.serveCtx = t.Context() t.Cleanup(func() { p.shutdown(t.Context()) }) _, err := p.enableFacade(enableFacadeParams{ - Engine: tc.engine, + Engine: engine, Port: freeTCPPort(t), - AliasAddresses: []string{tc.alias}, + AliasAddresses: []string{alias}, IgnorePersistedPort: true, }) require.Error(t, err, "enable accepted alias") // Rejected outright, not enabled-then-partially-configured. - assert.Nil(t, p.facadeFor(tc.engine), "a rejected alias still left a") + assert.Nil(t, p.facadeFor(engine), "a rejected alias must leave no facade enabled") }) } + test("a LAN address", aliasEngine.Name, "192.168.1.20:11433") + test("the wildcard address", aliasEngine.Name, "0.0.0.0:11433") + test("an engine with no inherited host variable", plainEngine.Name, "127.0.0.1:11433") } // Facade notifications are addressed, so the broker can attribute a bare diff --git a/services/nvpair-tui/ui/demo_test.go b/services/nvpair-tui/ui/demo_test.go index 76af1c81..2b3707f0 100644 --- a/services/nvpair-tui/ui/demo_test.go +++ b/services/nvpair-tui/ui/demo_test.go @@ -263,33 +263,23 @@ func TestGenerates_MirrorsTheDispatcher(t *testing.T) { // A model advertising neither a type nor capabilities gets the benefit of // the doubt, because that is what the dispatcher itself does — being // stricter here would silently exclude models the demo could have used. - cases := []struct { - name string - model dispatcherModel - want bool - }{ - {"explicit llm", dispatcherModel{Type: "LLM"}, true}, - {"explicit embedding", dispatcherModel{Type: "embeddings"}, false}, - {"chat capability", dispatcherModel{Capabilities: []string{"vision", "chat"}}, true}, - {"no generation capability", dispatcherModel{Capabilities: []string{"embedding"}}, false}, - {"nothing declared", dispatcherModel{}, true}, - {"type wins over capabilities", dispatcherModel{ - Type: "embeddings", Capabilities: []string{"chat"}}, false}, - // Verbatim from a live LM Studio: a real chat model whose advertised - // capabilities name neither chat nor completion. Checking capabilities - // ahead of the type would exclude the only usable model on the host, so - // the ordering above is load-bearing rather than arbitrary. - {"real llm with unrelated capabilities", dispatcherModel{ - Type: "llm", - Capabilities: []string{"trained_for_tool_use", "vision"}, - }, true}, - {"real embedding model", dispatcherModel{Type: "embedding"}, false}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.want, tc.model.generates()) + test := func(name string, model dispatcherModel, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, model.generates()) }) } + test("explicit llm", dispatcherModel{Type: "LLM"}, true) + test("explicit embedding", dispatcherModel{Type: "embeddings"}, false) + test("chat capability", dispatcherModel{Capabilities: []string{"vision", "chat"}}, true) + test("no generation capability", dispatcherModel{Capabilities: []string{"embedding"}}, false) + test("nothing declared", dispatcherModel{}, true) + test("type wins over capabilities", dispatcherModel{Type: "embeddings", Capabilities: []string{"chat"}}, false) + // Verbatim from a live LM Studio: a real chat model whose advertised + // capabilities name neither chat nor completion. Checking capabilities + // ahead of the type would exclude the only usable model on the host, so + // the ordering above is load-bearing rather than arbitrary. + test("real llm with unrelated capabilities", dispatcherModel{Type: "llm", Capabilities: []string{"trained_for_tool_use", "vision"}}, true) + test("real embedding model", dispatcherModel{Type: "embedding"}, false) } // fakeDispatcher writes an executable that records its arguments and prints the diff --git a/services/nvpair-tui/ui/engineswire_test.go b/services/nvpair-tui/ui/engineswire_test.go index b4872693..716a8a86 100644 --- a/services/nvpair-tui/ui/engineswire_test.go +++ b/services/nvpair-tui/ui/engineswire_test.go @@ -37,14 +37,18 @@ func actionOf(t *testing.T, envelope map[string]any) (string, map[string]any) { // `lms get {model}` CLI placeholder). Sending only "name" silently ran // `lms get "" --yes`, so the download never reached LM Studio. func TestPull_SendsBothKeys(t *testing.T) { - for _, engine := range []string{engines.NameOllama, engines.NameLMStudio} { - envelope := wireOf(t, engine, "pull", "owner/model") - require.Equal(t, engine, envelope["engine"]) - action, params := actionOf(t, envelope) - require.Equal(t, "pull_model", action, "%s", engine) - assert.Equal(t, "owner/model", params["name"], "%s: pull must set name", engine) - assert.Equal(t, "owner/model", params["model"], "%s: pull must set model", engine) + test := func(name, engine string) { + t.Run(name, func(t *testing.T) { + envelope := wireOf(t, engine, "pull", "owner/model") + require.Equal(t, engine, envelope["engine"]) + action, params := actionOf(t, envelope) + require.Equal(t, "pull_model", action) + assert.Equal(t, "owner/model", params["name"], "pull must set name") + assert.Equal(t, "owner/model", params["model"], "pull must set model") + }) } + test("Ollama", engines.NameOllama) + test("LM Studio", engines.NameLMStudio) } // TestOllamaLoadUsesRunModel guards the contract the two engines do NOT share. @@ -134,13 +138,17 @@ func TestDeadlineLeniency_TracksOperationLength(t *testing.T) { // TestDeleteSendsBothKeys checks delete works on either engine, since Ollama // keys it as "name" and LM Studio as "model". func TestDelete_SendsBothKeys(t *testing.T) { - for _, engine := range []string{engines.NameOllama, engines.NameLMStudio} { - envelope := wireOf(t, engine, "delete", "victim") - action, params := actionOf(t, envelope) - assert.Equal(t, "delete_model", action, "%s", engine) - assert.Equal(t, "victim", params["name"], "%s: delete must set name", engine) - assert.Equal(t, "victim", params["model"], "%s: delete must set model", engine) + test := func(name, engine string) { + t.Run(name, func(t *testing.T) { + envelope := wireOf(t, engine, "delete", "victim") + action, params := actionOf(t, envelope) + assert.Equal(t, "delete_model", action) + assert.Equal(t, "victim", params["name"], "delete must set name") + assert.Equal(t, "victim", params["model"], "delete must set model") + }) } + test("Ollama", engines.NameOllama) + test("LM Studio", engines.NameLMStudio) } // TestLlamaCPPSendsTheModelAlone checks every llama.cpp model operation carries @@ -148,16 +156,18 @@ func TestDelete_SendsBothKeys(t *testing.T) { // for it does. Its delete sends the params as a query string, so an extra // "name" would reach the engine as a parameter it does not take. func TestLlamaCPP_SendsTheModelAlone(t *testing.T) { - want := map[string]string{ - "load": "load_model", "unload": "unload_model", - "delete": "delete_model", "pull": "pull_model", - } - for op, action := range want { - envelope := wireOf(t, engines.NameLlamaCPP, op, "ggml-org/gemma-3-1b-it-GGUF:Q4_K_M") - got, params := actionOf(t, envelope) - assert.Equal(t, action, got, "%s", op) - assert.Equal(t, map[string]any{"model": "ggml-org/gemma-3-1b-it-GGUF:Q4_K_M"}, params, "%s: params must contain only the model", op) + test := func(name, op, action string) { + t.Run(name, func(t *testing.T) { + envelope := wireOf(t, engines.NameLlamaCPP, op, "ggml-org/gemma-3-1b-it-GGUF:Q4_K_M") + got, params := actionOf(t, envelope) + assert.Equal(t, action, got) + assert.Equal(t, map[string]any{"model": "ggml-org/gemma-3-1b-it-GGUF:Q4_K_M"}, params, "params must contain only the model") + }) } + test("load", "load", "load_model") + test("unload", "unload", "unload_model") + test("delete", "delete", "delete_model") + test("pull", "pull", "pull_model") } // TestDownloadPromptUsesTheEnginesSpelling is the regression guard for an diff --git a/services/nvpair-tui/ui/framebudget_test.go b/services/nvpair-tui/ui/framebudget_test.go index 63aea8af..401584b1 100644 --- a/services/nvpair-tui/ui/framebudget_test.go +++ b/services/nvpair-tui/ui/framebudget_test.go @@ -69,38 +69,37 @@ func TestNodesView_NeverOverflowsItsBudget(t *testing.T) { return v } - cases := map[string]func(*nodesView){ - "plain": func(*nodesView) {}, - "filter": func(v *nodesView) { v.filter = "node"; v.rebuild() }, - "warning": func(v *nodesView) { v.noteFeed(feedManual, errStub{}) }, - "status": func(v *nodesView) { v.status.error("something failed") }, - "inbound": func(v *nodesView) { v.inbound = &clusterInvite{FromNodeName: "peer"} }, - "editor": func(v *nodesView) { v.beginInput(nodesInputManualAddress, "host") }, - "filter+warning": func(v *nodesView) { - v.filter = "node" - v.rebuild() - v.noteFeed(feedManual, errStub{}) - }, - "everything": func(v *nodesView) { - v.filter = "node" - v.rebuild() - v.noteFeed(feedManual, errStub{}) - v.inbound = &clusterInvite{FromNodeName: "peer"} - v.beginInput(nodesInputManualAddress, "host") - v.status.error("something failed") - }, - "filter matches nothing": func(v *nodesView) { - v.filter = "no-such-node" - v.rebuild() - v.status.error("something failed") - }, - } - - for name, setup := range cases { - v := build() - setup(v) - assert.LessOrEqual(t, renderedRows(v.View()), budget, "%s: the shell must not delete the last lines", name) + test := func(name string, setup func(*nodesView)) { + t.Run(name, func(t *testing.T) { + v := build() + setup(v) + assert.LessOrEqual(t, renderedRows(v.View()), budget, "the shell must not delete the last lines") + }) } + test("plain", func(*nodesView) {}) + test("filter", func(v *nodesView) { v.filter = "node"; v.rebuild() }) + test("warning", func(v *nodesView) { v.noteFeed(feedManual, errStub{}) }) + test("status", func(v *nodesView) { v.status.error("something failed") }) + test("inbound", func(v *nodesView) { v.inbound = &clusterInvite{FromNodeName: "peer"} }) + test("editor", func(v *nodesView) { v.beginInput(nodesInputManualAddress, "host") }) + test("filter+warning", func(v *nodesView) { + v.filter = "node" + v.rebuild() + v.noteFeed(feedManual, errStub{}) + }) + test("everything", func(v *nodesView) { + v.filter = "node" + v.rebuild() + v.noteFeed(feedManual, errStub{}) + v.inbound = &clusterInvite{FromNodeName: "peer"} + v.beginInput(nodesInputManualAddress, "host") + v.status.error("something failed") + }) + test("filter matches nothing", func(v *nodesView) { + v.filter = "no-such-node" + v.rebuild() + v.status.error("something failed") + }) } // TestErrorsViewNeverOverflowsItsBudget covers the context line added for the @@ -129,14 +128,19 @@ func TestErrorsView_NeverOverflowsItsBudget(t *testing.T) { return v } - for _, n := range []int{0, 1, 30} { - v := build(n) - assert.LessOrEqual(t, renderedRows(v.View()), budget, "%d errors", n) + test := func(name string, n int) { + t.Run(name, func(t *testing.T) { + v := build(n) + assert.LessOrEqual(t, renderedRows(v.View()), budget) - v = build(n) - v.status.ok("cleared") - assert.LessOrEqual(t, renderedRows(v.View()), budget, "%d errors + status: the status line must not be cut", n) + v = build(n) + v.status.ok("cleared") + assert.LessOrEqual(t, renderedRows(v.View()), budget, "the status line must not be cut") + }) } + test("no errors", 0) + test("one error", 1) + test("many errors", 30) } // TestNodeDetailNeverOverflowsItsBudget covers the screen with two tables @@ -162,26 +166,25 @@ func TestNodeDetail_NeverOverflowsItsBudget(t *testing.T) { return d } - cases := map[string]func(*nodeDetail){ - "plain": func(*nodeDetail) {}, - "editor": func(d *nodeDetail) { d.mode = detailInputEnginePort }, - "status": func(d *nodeDetail) { d.status.error("start failed: no such engine") }, - "editor+status": func(d *nodeDetail) { - d.mode = detailInputModelName - d.status.error("download failed") - }, - "hardware": func(d *nodeDetail) { - d.telemetryOK = true - d.telemetry = nodeTelemetry{TelemetryValid: true} - d.SetSize(w, budget) - }, - } - - for name, setup := range cases { - d := build() - setup(d) - assert.LessOrEqual(t, renderedRows(d.View()), budget, "%s", name) + test := func(name string, setup func(*nodeDetail)) { + t.Run(name, func(t *testing.T) { + d := build() + setup(d) + assert.LessOrEqual(t, renderedRows(d.View()), budget) + }) } + test("plain", func(*nodeDetail) {}) + test("editor", func(d *nodeDetail) { d.mode = detailInputEnginePort }) + test("status", func(d *nodeDetail) { d.status.error("start failed: no such engine") }) + test("editor+status", func(d *nodeDetail) { + d.mode = detailInputModelName + d.status.error("download failed") + }) + test("hardware", func(d *nodeDetail) { + d.telemetryOK = true + d.telemetry = nodeTelemetry{TelemetryValid: true} + d.SetSize(w, budget) + }) } // TestEveryViewRendersWithinBudget is the blanket guard. diff --git a/services/nvpair-tui/ui/jobs_test.go b/services/nvpair-tui/ui/jobs_test.go index eb8b1a0c..6c73786c 100644 --- a/services/nvpair-tui/ui/jobs_test.go +++ b/services/nvpair-tui/ui/jobs_test.go @@ -144,10 +144,14 @@ func TestJobs_SortNewestFirst(t *testing.T) { // TestJobIDTellsSimultaneousJobsApart checks two jobs for the same model, from // the same node, started at the same moment, do not read as one row twice. func TestJobID_TellsSimultaneousJobsApart(t *testing.T) { - cases := map[string]string{"1": "1", "123456": "123456", "burst-1042": "…-1042"} - for id, want := range cases { - assert.Equal(t, want, shortJobID(id), "shortJobID(%q)", id) + test := func(name, id, want string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, shortJobID(id)) + }) } + test("short id", "1", "1") + test("id fits column", "123456", "123456") + test("long id keeps suffix", "burst-1042", "…-1042") v := newJobsView(nil) v.upsert(workload{ID: "7", Model: "m", OriginatedFrom: "n", State: "running", CreatedAt: 5}) diff --git a/services/nvpair-tui/ui/model_test.go b/services/nvpair-tui/ui/model_test.go index 4bf43b0e..82860830 100644 --- a/services/nvpair-tui/ui/model_test.go +++ b/services/nvpair-tui/ui/model_test.go @@ -52,25 +52,19 @@ func newTestModel(views ...View) Model { // frame scrolls the alt screen and leaves the previous frame's tail behind. func TestViewFrame_IsExactlyTerminalSized(t *testing.T) { const termWidth, termHeight = 80, 24 - cases := []struct { - name string - view *stubView - }{ - {"view renders far too many rows", &stubView{title: "Over", rows: 200, width: 40}}, - {"view renders too few rows", &stubView{title: "Under", rows: 1, width: 40}}, - {"view renders lines wider than the terminal", &stubView{title: "Wide", rows: 5, width: 500}}, - {"view renders nothing", &stubView{title: "Empty", rows: 0, width: 0}}, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - m := New(nil, nil, []View{tc.view}) + test := func(name string, view *stubView) { + t.Run(name, func(t *testing.T) { + m := New(nil, nil, []View{view}) m.width, m.height = termWidth, termHeight out := m.View() assert.Equal(t, termHeight, lipgloss.Height(out), "frame height") assert.LessOrEqual(t, lipgloss.Width(out), termWidth, "frame width") }) } + test("view renders far too many rows", &stubView{title: "Over", rows: 200, width: 40}) + test("view renders too few rows", &stubView{title: "Under", rows: 1, width: 40}) + test("view renders lines wider than the terminal", &stubView{title: "Wide", rows: 5, width: 500}) + test("view renders nothing", &stubView{title: "Empty", rows: 0, width: 0}) } // TestFrameNeverOutgrowsTheTerminal pushes the view's budget as low as it goes @@ -269,10 +263,15 @@ func TestJumpDigits_MatchTabOrder(t *testing.T) { // TestJumpDigitsLabelDegenerateCounts covers the label at the edges, since it is // assembled rather than written out. func TestJumpDigits_LabelsZeroAndSingleTabCounts(t *testing.T) { - cases := map[int]string{1: "1", 2: "1-2", 5: "1-5", 9: "1-9"} - for tabs, want := range cases { - assert.Equal(t, want, tabDigitsHelp(tabs), "%d tabs", tabs) + test := func(name string, tabs int, want string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, tabDigitsHelp(tabs)) + }) } + test("single tab", 1, "1") + test("two tabs", 2, "1-2") + test("five tabs", 5, "1-5") + test("all digit keys", 9, "1-9") } // closingView records being released. diff --git a/services/nvpair-tui/ui/nodedetail_test.go b/services/nvpair-tui/ui/nodedetail_test.go index a65bfee8..ca0790be 100644 --- a/services/nvpair-tui/ui/nodedetail_test.go +++ b/services/nvpair-tui/ui/nodedetail_test.go @@ -268,23 +268,18 @@ func TestSettingsEditors_AreDistinct(t *testing.T) { d.refreshEngines() seedSettings(d, ollamaSettings()) - cases := []struct { - key string - mode detailInputMode - value string - label string - }{ - {"e", detailInputEnginePort, "11434", "engine"}, - {"p", detailInputProxyPort, "11435", "proxy"}, - {"a", detailInputLaunchArgs, "OLLAMA_KEEP_ALIVE=5m", "arguments"}, - } - for _, tc := range cases { - d.mode = detailInputNone - d.handleEngineKey(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(tc.key)}) - require.Equal(t, tc.mode, d.mode, "%q editor mode", tc.key) - assert.Equal(t, tc.value, d.input.Value(), "%q initial value", tc.key) - assert.Contains(t, d.inputLabel(), tc.label, "label identifies field") + test := func(name, key string, mode detailInputMode, value, label string) { + t.Run(name, func(t *testing.T) { + d.mode = detailInputNone + d.handleEngineKey(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)}) + require.Equal(t, mode, d.mode) + assert.Equal(t, value, d.input.Value()) + assert.Contains(t, d.inputLabel(), label, "label identifies field") + }) } + test("engine port", "e", detailInputEnginePort, "11434", "engine") + test("proxy port", "p", detailInputProxyPort, "11435", "proxy") + test("launch arguments", "a", detailInputLaunchArgs, "OLLAMA_KEEP_ALIVE=5m", "arguments") } // TestSettingsRefusalComesFromTheBackend checks an engine the backend will not @@ -325,34 +320,16 @@ func TestSettingsWrite_CarriesTheRevisionAndResolution(t *testing.T) { snap := ollamaSettings() seedSettings(d, snap) - cases := []struct { - name string - mode detailInputMode - value string - resolution string - check func(enginesettings.Config) error - }{ - { - name: "the port field wins over the command", - mode: detailInputEnginePort, - value: "11500", - resolution: resolutionServer, - }, - { - name: "the command wins over the port field", - mode: detailInputLaunchArgs, - value: "OLLAMA_HOST=127.0.0.1:11500", - resolution: resolutionLaunch, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - req, ok := d.settingsRequest(snap, tc.mode, tc.value) + test := func(name string, mode detailInputMode, value, resolution string) { + t.Run(name, func(t *testing.T) { + req, ok := d.settingsRequest(snap, mode, value) require.True(t, ok, "the request was rejected") assert.Equal(t, snap.Revision, req.ExpectedRevision, "snapshot revision") - assert.Equal(t, tc.resolution, req.Resolution) + assert.Equal(t, resolution, req.Resolution) }) } + test("the port field wins over the command", detailInputEnginePort, "11500", resolutionServer) + test("the command wins over the port field", detailInputLaunchArgs, "OLLAMA_HOST=127.0.0.1:11500", resolutionLaunch) } // TestLaunchTextIsSentAsTyped checks the arguments field is not tidied here. @@ -533,39 +510,23 @@ func TestSettingsRestart_IsConfirmed(t *testing.T) { // TestSettingsPreviewFailuresAreExplained checks a rejected draft says why and // saves nothing. func TestSettingsPreviewFailures_AreExplained(t *testing.T) { - cases := []struct { - name string - preview enginesettings.Preview - err error - want string - }{ - { - name: "a field the backend rejected", - preview: enginesettings.Preview{Errors: map[string]string{"launchText": "unbalanced quote"}}, - want: "unbalanced quote", - }, - { - name: "the two ports disagree", - preview: enginesettings.Preview{Conflict: &enginesettings.Conflict{ServerPort: 11434, LaunchPort: 11500}}, - want: "11500", - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, preview enginesettings.Preview, want string) { + t.Run(name, func(t *testing.T) { d := localDetail() d.engines = []engineStatus{{Engine: "ollama", Installed: true}} d.refreshEngines() cmd := d.applySettingsPreview(enginePreviewMsg{ request: enginesettings.Request{Engine: "ollama"}, - preview: tc.preview, - err: tc.err, + preview: preview, }) assert.Nil(t, cmd, "a rejected draft was sent anyway") assert.Nil(t, d.settingsConfirm, "a rejected draft was armed for confirmation") - assert.Contains(t, d.status.render(), tc.want) + assert.Contains(t, d.status.render(), want) }) } + test("a field the backend rejected", enginesettings.Preview{Errors: map[string]string{"launchText": "unbalanced quote"}}, "unbalanced quote") + test("the two ports disagree", enginesettings.Preview{Conflict: &enginesettings.Conflict{ServerPort: 11434, LaunchPort: 11500}}, "11500") } // TestSettingsErrorsReadTheSameEveryTime checks the per-field errors are @@ -1174,25 +1135,23 @@ func TestEmptyEngineList_ExplainsItself(t *testing.T) { // rendered as "not answering" — beside a hardware readout, polled over an // endpoint that needs no pairing, visibly updating for the same machine. func TestUnpairedNode_IsNotCalledSilent(t *testing.T) { - cases := map[nodeMembership]string{ - membershipNone: "not in this cluster", - membershipForeign: "another cluster", - membershipPending: "still pairing", - } - for membership, want := range cases { - d := remoteDetail() - d.node.membership = membership - - assert.Nil(t, d.enginesCmd(), "%v: asked for engines over a link that cannot carry the question", membership) - got := d.emptyEnginesHint() - assert.Contains(t, got, want, "%v: unpaired hint", membership) - assert.NotContains(t, got, "not answering", "%v: hint calls a reachable node silent", membership) - // Models come from what the node advertises over discovery, which - // needs no pairing, so the models pane must not blame a stopped engine - // for what it cannot see either way. - d.node.presence = presenceOnline - assert.NotContains(t, d.emptyModelsHint(), "start an engine", "%v: models hint claims an engine needs starting", membership) + test := func(name string, membership nodeMembership, want string) { + t.Run(name, func(t *testing.T) { + d := remoteDetail() + d.node.membership = membership + assert.Nil(t, d.enginesCmd(), "asked for engines over a link that cannot carry the question") + got := d.emptyEnginesHint() + assert.Contains(t, got, want) + assert.NotContains(t, got, "not answering", "hint calls a reachable node silent") + // Discovery needs no pairing, so the models pane must not blame a + // stopped engine for what it cannot see either way. + d.node.presence = presenceOnline + assert.NotContains(t, d.emptyModelsHint(), "start an engine", "models hint claims an engine needs starting") + }) } + test("standalone node", membershipNone, "not in this cluster") + test("foreign cluster member", membershipForeign, "another cluster") + test("pairing in progress", membershipPending, "still pairing") // A member is still asked, and is still allowed to be silent. member := remoteDetail() @@ -1444,17 +1403,29 @@ func (d *nodeDetail) handleKeyForTest(t *testing.T, k string) tea.Cmd { } func TestParsePort_RejectsInvalidAndOutOfRangeValues(t *testing.T) { - valid := map[string]int{"1": 1, "11434": 11434, "65535": 65535} - for in, want := range valid { - got, ok := parsePort(in) - if assert.True(t, ok, "parsePort(%q)", in) { - assert.Equal(t, want, got, "parsePort(%q)", in) - } + accept := func(name, in string, want int) { + t.Run(name, func(t *testing.T) { + got, ok := parsePort(in) + if assert.True(t, ok) { + assert.Equal(t, want, got) + } + }) } - for _, in := range []string{"", "0", "-1", "65536", "abc", "80x"} { - _, ok := parsePort(in) - assert.False(t, ok, "parsePort(%q) accepted an invalid port", in) + reject := func(name, in string) { + t.Run(name, func(t *testing.T) { + _, ok := parsePort(in) + assert.False(t, ok, "accepted an invalid port") + }) } + accept("minimum port", "1", 1) + accept("usual engine port", "11434", 11434) + accept("maximum port", "65535", 65535) + reject("empty", "") + reject("zero", "0") + reject("negative", "-1") + reject("above maximum", "65536") + reject("not a number", "abc") + reject("trailing text", "80x") } // TestServiceTabNoLongerOffersPorts checks the ports are configured in exactly diff --git a/services/nvpair-tui/ui/nodesmodel_test.go b/services/nvpair-tui/ui/nodesmodel_test.go index 6cb42707..ed29cd33 100644 --- a/services/nvpair-tui/ui/nodesmodel_test.go +++ b/services/nvpair-tui/ui/nodesmodel_test.go @@ -47,23 +47,22 @@ func TestFilterNodeRows_MatchesNameAndAddress(t *testing.T) { {key: "c", name: "Server-01", address: "10.0.1.7"}, } - cases := map[string][]string{ - "work": {"a"}, // name substring - "10.0.0.": {"a", "b"}, // shared address prefix - "192.168.1.9": {"a"}, // a secondary address - "SERVER": {"c"}, // case-insensitive - " laptop ": {"b"}, // surrounding whitespace ignored - "nothing": {}, - } - for needle, want := range cases { - got := filterNodeRows(rows, needle) - if !assert.Len(t, got, len(want), "filter %q", needle) { - continue - } - for i, key := range want { - assert.Equal(t, key, got[i].key, "filter %q row %d", needle, i) - } + test := func(name, needle string, want ...string) { + t.Run(name, func(t *testing.T) { + got := filterNodeRows(rows, needle) + keys := make([]string, len(got)) + for i, row := range got { + keys[i] = row.key + } + assert.Equal(t, append([]string{}, want...), keys) + }) } + test("name substring", "work", "a") + test("shared address prefix", "10.0.0.", "a", "b") + test("secondary address", "192.168.1.9", "a") + test("case insensitive name", "SERVER", "c") + test("surrounding whitespace", " laptop ", "b") + test("no matches", "nothing") // An empty filter is not a filter. assert.Len(t, filterNodeRows(rows, " "), len(rows), "blank filter must preserve rows") @@ -251,15 +250,15 @@ func TestTLSNodeInfo_IsNotPolledInPlainText(t *testing.T) { // TestMembershipGovernsInvitability is the guard for re-inviting a node that // already has a relationship. Only a standalone node may be invited. func TestNodeInvitation_RequiresEligibleClusterMembership(t *testing.T) { - cases := map[nodeMembership]bool{ - membershipNone: true, - membershipMember: false, - membershipForeign: false, - membershipPending: false, - } - for membership, want := range cases { - assert.Equal(t, want, membership.invitable(), "%v", membership) + test := func(name string, membership nodeMembership, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, membership.invitable()) + }) } + test("standalone node can be invited", membershipNone, true) + test("cluster member cannot be invited", membershipMember, false) + test("foreign member cannot be invited", membershipForeign, false) + test("pairing node cannot be invited", membershipPending, false) } // TestMergeMarksSelf checks this machine is identified and always reads online. diff --git a/services/nvpair-tui/ui/nodetelemetry_test.go b/services/nvpair-tui/ui/nodetelemetry_test.go index fc80526d..35718854 100644 --- a/services/nvpair-tui/ui/nodetelemetry_test.go +++ b/services/nvpair-tui/ui/nodetelemetry_test.go @@ -200,9 +200,14 @@ func TestTelemetryFromAnotherMachine_IsRefused(t *testing.T) { assert.NoError(t, pollTelemetryCmd("manual:1", 1, []string{host}, port, "")().(nodeTelemetryMsg).err, "an unchecked poll was refused") assert.Equal(t, "uuid-a", telemetryIdentity(nodeRow{key: "uuid-a"}), "a discovered node is checked against its UUID") - for _, row := range []nodeRow{{key: "manual:1"}, {key: "name:lab"}, {key: "uuid-self", self: true}} { - assert.Empty(t, telemetryIdentity(row), "%q has nothing to check", row.key) + test := func(name string, row nodeRow) { + t.Run(name, func(t *testing.T) { + assert.Empty(t, telemetryIdentity(row), "row has nothing to check") + }) } + test("manual entry", nodeRow{key: "manual:1"}) + test("name-only entry", nodeRow{key: "name:lab"}) + test("self entry", nodeRow{key: "uuid-self", self: true}) } // TestTelemetryTriesTheAddressThatAnswered is the regression guard for a poll @@ -211,9 +216,14 @@ func TestTelemetryFromAnotherMachine_IsRefused(t *testing.T) { func TestTelemetry_TriesTheAddressThatAnswered(t *testing.T) { addresses := []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"} assert.Equal(t, []string{"10.0.0.3", "10.0.0.1", "10.0.0.2"}, preferAddress(addresses, "10.0.0.3"), "address that answered first") - for _, last := range []string{"", "10.0.0.1", "10.9.9.9"} { - assert.Equal(t, []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}, preferAddress(addresses, last), "after %q: node's own order", last) + test := func(name, last string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}, preferAddress(addresses, last), "node's own order") + }) } + test("no address answered", "") + test("first address answered", "10.0.0.1") + test("unlisted address answered", "10.9.9.9") assert.Equal(t, []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}, addresses, "the node's own address list was reordered in place") d := newNodeDetail(nil, nodeRow{key: "peer", addresses: addresses, presence: presenceOnline}) @@ -244,20 +254,20 @@ func TestTelemetrySummary_HandlesMissingMetrics(t *testing.T) { } func TestHumanBytes_FormatsByteCounts(t *testing.T) { - cases := map[uint64]string{ - 0: "0 B", - 512: "512 B", - 1024: "1.0 KiB", - 1 << 30: "1.0 GiB", - 8 * (1 << 30): "8.0 GiB", - 32 * (1 << 30): "32.0 GiB", - // At three digits the decimal stops earning its place. - 128 * (1 << 30): "128 GiB", - 4 * (1 << 40): "4.0 TiB", - } - for in, want := range cases { - assert.Equal(t, want, humanBytes(in), "humanBytes(%d)", in) + test := func(name string, in uint64, want string) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, humanBytes(in)) + }) } + test("zero bytes", 0, "0 B") + test("bytes below one KiB", 512, "512 B") + test("one KiB", 1024, "1.0 KiB") + test("one GiB", 1<<30, "1.0 GiB") + test("eight GiB", 8*(1<<30), "8.0 GiB") + test("thirty-two GiB", 32*(1<<30), "32.0 GiB") + // At three digits the decimal stops earning its place. + test("three digits omit the decimal", 128*(1<<30), "128 GiB") + test("four TiB", 4*(1<<40), "4.0 TiB") } // TestDetailHardwareUnavailableWhenPollFails checks the panel is explicit rather diff --git a/services/nvpair-tui/ui/service_test.go b/services/nvpair-tui/ui/service_test.go index 795aaf3d..6fd35234 100644 --- a/services/nvpair-tui/ui/service_test.go +++ b/services/nvpair-tui/ui/service_test.go @@ -592,10 +592,17 @@ func TestHiddenWorkers_ExcludesHeaderFromVisibleCapacity(t *testing.T) { // TestVisibleTableRows pins the header accounting the layout budgets depend on. func TestVisibleTableRows_AccountsForTerminalHeight(t *testing.T) { - cases := map[int]int{0: 0, 1: 0, 2: 0, 3: 1, 5: 3, 13: 11} - for h, want := range cases { - assert.Equal(t, want, visibleTableRows(h), "visibleTableRows(%d)", h) + test := func(name string, height, want int) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, visibleTableRows(height)) + }) } + test("zero height", 0, 0) + test("header only", 1, 0) + test("header and border only", 2, 0) + test("one data row", 3, 1) + test("three data rows", 5, 3) + test("eleven data rows", 13, 11) } var _ View = (*serviceView)(nil) diff --git a/services/nvpair-tui/ui/styles_test.go b/services/nvpair-tui/ui/styles_test.go index 0ab558b0..b50abb5f 100644 --- a/services/nvpair-tui/ui/styles_test.go +++ b/services/nvpair-tui/ui/styles_test.go @@ -24,11 +24,7 @@ import ( // a test process is unset and lipgloss then drops colour from the output // entirely: the rendered strings for a right and a wrong pairing are identical. func TestTextOnAnAdaptiveBackground_AdaptsToo(t *testing.T) { - cases := map[string]lipgloss.Style{ - "active tab": tabActiveStyle, - "selected table row": tableStyles().Selected, - } - for name, style := range cases { + test := func(name string, style lipgloss.Style) { t.Run(name, func(t *testing.T) { _, bgAdaptive := style.GetBackground().(lipgloss.AdaptiveColor) if !bgAdaptive { @@ -42,6 +38,8 @@ func TestTextOnAnAdaptiveBackground_AdaptsToo(t *testing.T) { assert.NotEqual(t, fg.Light, fg.Dark, "foreground must suit both the light and dark backgrounds") }) } + test("active tab", tabActiveStyle) + test("selected table row", tableStyles().Selected) } // TestAppearanceOverrideIsExplicit checks the flag accepts exactly the three @@ -53,24 +51,31 @@ func TestTextOnAnAdaptiveBackground_AdaptsToo(t *testing.T) { // so falling back to detection leaves them exactly where they started with no // indication why. func TestAppearanceOverride_IsExplicit(t *testing.T) { - good := map[string]Appearance{ - "auto": AppearanceAuto, - "": AppearanceAuto, - "light": AppearanceLight, - "dark": AppearanceDark, - " Dark ": AppearanceDark, - "LIGHT": AppearanceLight, - } - for in, want := range good { - got, ok := ParseAppearance(in) - assert.True(t, ok, "appearance %q", in) - assert.Equal(t, want, got, "appearance %q", in) + accept := func(name, in string, want Appearance) { + t.Run(name, func(t *testing.T) { + got, ok := ParseAppearance(in) + assert.True(t, ok) + assert.Equal(t, want, got) + }) } - - for _, in := range []string{"lite", "black", "white", "true", "1", "no"} { - _, ok := ParseAppearance(in) - assert.False(t, ok, "appearance %q should be refused", in) + reject := func(name, in string) { + t.Run(name, func(t *testing.T) { + _, ok := ParseAppearance(in) + assert.False(t, ok, "appearance must be refused") + }) } + accept("automatic detection", "auto", AppearanceAuto) + accept("empty uses detection", "", AppearanceAuto) + accept("light", "light", AppearanceLight) + accept("dark", "dark", AppearanceDark) + accept("case and whitespace", " Dark ", AppearanceDark) + accept("uppercase", "LIGHT", AppearanceLight) + reject("misspelled light", "lite") + reject("background color black", "black") + reject("background color white", "white") + reject("boolean true", "true") + reject("numeric boolean", "1") + reject("boolean no", "no") } // TestStartAppearanceSettlesBeforeTheFirstFrame checks the background is diff --git a/services/nvpair-tui/ui/table_test.go b/services/nvpair-tui/ui/table_test.go index 570b9966..0fc54cf2 100644 --- a/services/nvpair-tui/ui/table_test.go +++ b/services/nvpair-tui/ui/table_test.go @@ -18,29 +18,22 @@ import ( // and CPU model names reach it, a byte slice can land inside a multi-byte // sequence, and its callers pad with %-Ns, which counts runes too. func TestTruncate_PreservesDisplayWidth(t *testing.T) { - cases := []struct { - name string - in string - max int - want string - }{ - {"fits", "abc", 5, "abc"}, - {"exactly fits", "abcde", 5, "abcde"}, - {"cut with an ellipsis", "abcdef", 5, "abcd…"}, - {"multi-byte cut stays on a rune boundary", "ααααααααα™", 5, "αααα…"}, - {"multi-byte that fits is untouched", "ααα", 5, "ααα"}, - {"one column is only the ellipsis", "abc", 1, "…"}, - {"zero columns is empty", "abc", 0, ""}, - {"negative columns is empty", "abc", -1, ""}, - {"empty stays empty", "", 5, ""}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - got := truncate(tc.in, tc.max) - assert.Equal(t, tc.want, got) + test := func(name, in string, max int, want string) { + t.Run(name, func(t *testing.T) { + got := truncate(in, max) + assert.Equal(t, want, got) assert.True(t, utf8.ValidString(got), "truncate must produce valid UTF-8") }) } + test("fits", "abc", 5, "abc") + test("exactly fits", "abcde", 5, "abcde") + test("cut with an ellipsis", "abcdef", 5, "abcd…") + test("multi-byte cut stays on a rune boundary", "ααααααααα™", 5, "αααα…") + test("multi-byte that fits is untouched", "ααα", 5, "ααα") + test("one column is only the ellipsis", "abc", 1, "…") + test("zero columns is empty", "abc", 0, "") + test("negative columns is empty", "abc", -1, "") + test("empty stays empty", "", 5, "") } // rendered is the terminal width a laid-out row actually consumes: every column @@ -59,55 +52,15 @@ func rendered(cols []column, total int) int { // width it was given, never more. Sizing that ignores cellPadding overflows and // the terminal drops the rightmost columns. func TestLayoutColumns_FillsWidthExactly(t *testing.T) { - cases := []struct { - name string - total int - cols []column - }{ - { - name: "proxies upstream table", - total: 80, - cols: []column{ - flexCol("ID", 10, 1), - flexCol("HOST", 10, 1), - fixedCol("PORT", 7), - }, - }, - { - name: "nodes table", - total: 100, - cols: []column{ - flexCol("NAME", 10, 1), - flexCol("ADDRESS", 10, 1), - fixedCol("PORT", 7), - fixedCol("LAST SEEN", 10), - fixedCol("STATUS", 11), - }, - }, - { - name: "single flex column takes the remainder", - total: 60, - cols: []column{ - fixedCol("SEV", 9), - flexCol("MESSAGE", 10, 1), - }, - }, - { - name: "uneven division leaves no gap", - total: 77, - cols: []column{ - flexCol("A", 5, 1), - flexCol("B", 5, 1), - flexCol("C", 5, 1), - }, - }, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.total, rendered(tc.cols, tc.total), "row must consume the terminal width") + test := func(name string, total int, cols ...column) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, total, rendered(cols, total), "row must consume the terminal width") }) } + test("proxies upstream table", 80, flexCol("ID", 10, 1), flexCol("HOST", 10, 1), fixedCol("PORT", 7)) + test("nodes table", 100, flexCol("NAME", 10, 1), flexCol("ADDRESS", 10, 1), fixedCol("PORT", 7), fixedCol("LAST SEEN", 10), fixedCol("STATUS", 11)) + test("single flex column takes the remainder", 60, fixedCol("SEV", 9), flexCol("MESSAGE", 10, 1)) + test("uneven division leaves no gap", 77, flexCol("A", 5, 1), flexCol("B", 5, 1), flexCol("C", 5, 1)) } // TestLayoutColumnsWeightedFlex checks a heavier column takes proportionally @@ -126,11 +79,17 @@ func TestLayoutColumns_DistributesRemainingWidthByWeight(t *testing.T) { // would render as zero-width or negative. func TestLayoutColumns_FitsNarrowTerminal(t *testing.T) { cols := []column{fixedCol("SEV", 9), flexCol("MESSAGE", 10, 1)} - for _, total := range []int{0, 1, 10, 20} { - got := layoutColumns(total, cols) - assert.Equal(t, 9, got[0].Width, "total=%d: SEV must retain its declared width", total) - assert.Equal(t, 10, got[1].Width, "total=%d: MESSAGE must retain its minimum width", total) + test := func(name string, total int) { + t.Run(name, func(t *testing.T) { + got := layoutColumns(total, cols) + assert.Equal(t, 9, got[0].Width, "SEV must retain its declared width") + assert.Equal(t, 10, got[1].Width, "MESSAGE must retain its minimum width") + }) } + test("zero width", 0) + test("one column", 1) + test("fixed column barely fits", 10) + test("both minimums barely fit", 20) } // TestLayoutColumnsHonoursMinimum checks a flex column never shrinks below its @@ -188,20 +147,20 @@ func TestTableViews_AcceptRowsBeforeResize(t *testing.T) { // TestEveryTableViewHasColumnsAtConstruction is the direct invariant behind the // panic above, stated per view so a new view cannot regress it silently. func TestEveryTableView_HasColumnsAtConstruction(t *testing.T) { - widths := map[string][]table.Column{ - "nodes": nodesColumns(defaultTableWidth), - "jobs": workloadColumns(defaultTableWidth), - "detail engines local": detailEngineColumns(defaultTableWidth, false), - "detail engines remote": detailEngineColumns(defaultTableWidth, true), - "detail models": detailModelColumns(defaultTableWidth), - "service workers": serviceWorkerColumns(defaultTableWidth), - } - for name, cols := range widths { - assert.NotEmpty(t, cols, "%s: no columns", name) - for _, c := range cols { - assert.GreaterOrEqual(t, c.Width, minCellWidth, "%s: column %q", name, c.Title) - } + test := func(name string, cols []table.Column) { + t.Run(name, func(t *testing.T) { + assert.NotEmpty(t, cols, "no columns") + for _, c := range cols { + assert.GreaterOrEqual(t, c.Width, minCellWidth, "column %q", c.Title) + } + }) } + test("nodes", nodesColumns(defaultTableWidth)) + test("jobs", workloadColumns(defaultTableWidth)) + test("detail engines local", detailEngineColumns(defaultTableWidth, false)) + test("detail engines remote", detailEngineColumns(defaultTableWidth, true)) + test("detail models", detailModelColumns(defaultTableWidth)) + test("service workers", serviceWorkerColumns(defaultTableWidth)) } // TestRowsMatchTheirColumns pins the two halves of a table together. @@ -237,20 +196,24 @@ func TestTableRows_MatchDeclaredColumnWidths(t *testing.T) { assertRowWidths(t, v.workers) }) t.Run("node detail engines and models", func(t *testing.T) { - for _, remote := range []bool{false, true} { - d := newNodeDetail(nil, nodeRow{ - key: "u", - name: "n", - self: !remote, - modelsByEngine: map[string][]string{"ollama": {"llama3.2"}}, - loadedByEngine: map[string][]string{"ollama": {"llama3.2"}}, + test := func(name string, remote bool) { + t.Run(name, func(t *testing.T) { + d := newNodeDetail(nil, nodeRow{ + key: "u", + name: "n", + self: !remote, + modelsByEngine: map[string][]string{"ollama": {"llama3.2"}}, + loadedByEngine: map[string][]string{"ollama": {"llama3.2"}}, + }) + d.engines = []engineStatus{{Engine: "ollama", Installed: true}} + d.refreshEngines() + d.refreshModels() + assertRowWidths(t, d.engineTable) + assertRowWidths(t, d.modelTable) }) - d.engines = []engineStatus{{Engine: "ollama", Installed: true}} - d.refreshEngines() - d.refreshModels() - assertRowWidths(t, d.engineTable) - assertRowWidths(t, d.modelTable) } + test("local", false) + test("remote", true) }) } diff --git a/services/nvpair-tui/ui/toast_test.go b/services/nvpair-tui/ui/toast_test.go index a01ab971..43069d23 100644 --- a/services/nvpair-tui/ui/toast_test.go +++ b/services/nvpair-tui/ui/toast_test.go @@ -73,19 +73,18 @@ func TestToastSet_ReplacesPinnedToast(t *testing.T) { } func TestInviteOutcome_LabelsTerminalInviteEvents(t *testing.T) { - resolved := []string{ - "cluster:invite-declined", - "cluster:invite-expired", - "cluster:invite-canceled", - "cluster:invite-failed", - } - for _, method := range resolved { - outcome, ok := inviteOutcome(method) - if !assert.True(t, ok, "%s is not recognised as a terminal invite event", method) { - continue - } - assert.NotEmpty(t, outcome.label, "%s has no operator-facing label", method) + test := func(name, method string) { + t.Run(name, func(t *testing.T) { + outcome, ok := inviteOutcome(method) + if assert.True(t, ok, "recognised as a terminal invite event") { + assert.NotEmpty(t, outcome.label, "operator-facing label") + } + }) } + test("declined", "cluster:invite-declined") + test("expired", "cluster:invite-expired") + test("canceled", "cluster:invite-canceled") + test("failed", "cluster:invite-failed") // An invite arriving is not an invite resolving. _, ok := inviteOutcome("cluster:invite-received") @@ -286,12 +285,18 @@ func TestAddressMatches_RecognizesEquivalentAddresses(t *testing.T) { address: "10.0.0.7", addresses: []string{"10.0.0.7", "192.168.1.9"}, } - for _, in := range []string{"10.0.0.7", "10.0.0.7:14321", "192.168.1.9", "HOST-A"} { - assert.True(t, addressMatches(row, in), "addressMatches(%q)", in) - } - for _, in := range []string{"", "10.0.0.8", "other-host"} { - assert.False(t, addressMatches(row, in), "addressMatches(%q)", in) + test := func(name, in string, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, addressMatches(row, in)) + }) } + test("primary address", "10.0.0.7", true) + test("address with port", "10.0.0.7:14321", true) + test("secondary address", "192.168.1.9", true) + test("case insensitive hostname", "HOST-A", true) + test("empty", "", false) + test("different address", "10.0.0.8", false) + test("different hostname", "other-host", false) } // TestRemoveMemberRequiresConfirmation checks un-pairing a peer is armed rather @@ -682,13 +687,17 @@ func TestRejectionAdvice_MatchesTheReason(t *testing.T) { v.Update(nodeInviteMsg{name: "peer", rejected: true, reason: reasonAlreadyClustered}) assert.Contains(t, v.status.render(), "leave that cluster", "an already-clustered peer is told what to do") - for _, reason := range []string{"", "evil-arbitrary-text"} { - v.Update(nodeInviteMsg{name: "peer", rejected: true, reason: reason}) - got := v.status.render() - assert.NotContains(t, got, "leave that cluster", "reason %q got advice meant for an already-clustered peer", reason) - assert.NotContains(t, got, "relationship", "reason %q got advice meant for an already-clustered peer", reason) - assert.Contains(t, got, "rejected the invite", "reason %q: rejection is reported", reason) + test := func(name, reason string) { + t.Run(name, func(t *testing.T) { + v.Update(nodeInviteMsg{name: "peer", rejected: true, reason: reason}) + got := v.status.render() + assert.NotContains(t, got, "leave that cluster", "advice meant for an already-clustered peer") + assert.NotContains(t, got, "relationship", "advice meant for an already-clustered peer") + assert.Contains(t, got, "rejected the invite", "rejection is reported") + }) } + test("no reason", "") + test("unrecognised reason", "evil-arbitrary-text") } // TestInviteThatDidNotGoOutIsNotReportedSent is the regression guard for a @@ -696,18 +705,21 @@ func TestRejectionAdvice_MatchesTheReason(t *testing.T) { // when the first exchange does not complete, and anything short of "rejected" // was reported as "invite sent" and left pending. func TestInviteThatDidNotGoOut_IsNotReportedSent(t *testing.T) { - for _, state := range []string{"failed", "canceled"} { - v := newNodesView(nil) - v.SetSize(120, 30) - v.invitedKey, v.inviteSending = "peer-uuid", true - - v.Update(inviteResultMsg("peer", "", inviteNodeResult{InviteID: "inv", State: state}, nil)) - got := v.status.render() - assert.NotContains(t, got, "invite sent", "a %q reply", state) - assert.Contains(t, got, "did not go out", "a %q reply", state) - assert.Empty(t, v.invitedKey, "a %q reply left the invite pending", state) - assert.Empty(t, v.outboundInviteID, "a %q reply left the invite pending", state) + test := func(name, state string) { + t.Run(name, func(t *testing.T) { + v := newNodesView(nil) + v.SetSize(120, 30) + v.invitedKey, v.inviteSending = "peer-uuid", true + v.Update(inviteResultMsg("peer", "", inviteNodeResult{InviteID: "inv", State: state}, nil)) + got := v.status.render() + assert.NotContains(t, got, "invite sent") + assert.Contains(t, got, "did not go out") + assert.Empty(t, v.invitedKey, "invite must not remain pending") + assert.Empty(t, v.outboundInviteID, "invite must not remain pending") + }) } + test("failed", "failed") + test("canceled", "canceled") pin := "123456" v := newNodesView(nil) @@ -757,21 +769,18 @@ func TestWrongPin_IsNotReportedAsSuccess(t *testing.T) { // TestPairingOutcomesAreDistinguished checks each terminal state gets its own // answer, since they call for different things from the operator. func TestPairingOutcomes_AreDistinguished(t *testing.T) { - cases := []struct { - state, reason string - want string - }{ - {state: "paired", want: "paired with peer"}, - {state: "failed", reason: reasonIncorrectPIN, want: "wrong PIN"}, - {state: "failed", reason: "unreachable", want: "failed"}, - {state: "declined", want: "declined"}, - } - for _, tc := range cases { - v := newNodesView(nil) - v.SetSize(100, 30) - v.Update(pairingResultMsg{from: "peer", state: tc.state, reason: tc.reason}) - assert.Contains(t, v.status.render(), tc.want, "state=%q reason=%q", tc.state, tc.reason) + test := func(name, state, reason, want string) { + t.Run(name, func(t *testing.T) { + v := newNodesView(nil) + v.SetSize(100, 30) + v.Update(pairingResultMsg{from: "peer", state: state, reason: reason}) + assert.Contains(t, v.status.render(), want) + }) } + test("paired", "paired", "", "paired with peer") + test("incorrect PIN", "failed", reasonIncorrectPIN, "wrong PIN") + test("unreachable", "failed", "unreachable", "failed") + test("declined", "declined", "", "declined") } // TestNodesViewClearsInboundInviteOnExpiry checks an inbound prompt stops @@ -832,17 +841,10 @@ func TestNodesViewSelection_SurvivesReorder(t *testing.T) { // discovered-node path only, so the by-address path could still send a doomed // invite. func TestNodesView_GuardsInviteOnEveryPath(t *testing.T) { - cases := []struct { - name string - node availableNode - }{ - {"already a member of our cluster", availableNode{HostUUID: "k", Name: "peer", Trusted: true}}, - {"a member of another cluster", availableNode{HostUUID: "k", Name: "peer", Clustered: true}}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, node availableNode) { + t.Run(name, func(t *testing.T) { v := newNodesView(nil) - v.feeds.discovered = []availableNode{tc.node} + v.feeds.discovered = []availableNode{node} v.rebuild() v.selectedKey = "k" @@ -851,6 +853,8 @@ func TestNodesView_GuardsInviteOnEveryPath(t *testing.T) { assert.NotEmpty(t, v.status.render(), "invite blocked with no explanation to the operator") }) } + test("already a member of our cluster", availableNode{HostUUID: "k", Name: "peer", Trusted: true}) + test("a member of another cluster", availableNode{HostUUID: "k", Name: "peer", Clustered: true}) } // TestNodesViewRefusesSelfInvite checks this machine cannot be invited to its diff --git a/services/nvpair-tui/ui/updatecheck_test.go b/services/nvpair-tui/ui/updatecheck_test.go index 753fcff3..1f6ea57b 100644 --- a/services/nvpair-tui/ui/updatecheck_test.go +++ b/services/nvpair-tui/ui/updatecheck_test.go @@ -93,18 +93,19 @@ func TestFetchLatestRelease_ReadsTheTag(t *testing.T) { } func TestFetchLatestRelease_IgnoresDraftsAndPrereleases(t *testing.T) { - for _, body := range []string{ - `{"tag_name":"v0.92.0","draft":true,"prerelease":false}`, - `{"tag_name":"v0.92.0","draft":false,"prerelease":true}`, - } { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write([]byte(body)) - })) - got, err := fetchLatestRelease(srv.URL) - srv.Close() - require.NoError(t, err, "fetchLatestRelease") - assert.Empty(t, got, "drafts and prereleases must be ignored: %s", body) + test := func(name, body string) { + t.Run(name, func(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(body)) + })) + defer srv.Close() + got, err := fetchLatestRelease(srv.URL) + require.NoError(t, err, "fetchLatestRelease") + assert.Empty(t, got) + }) } + test("draft", `{"tag_name":"v0.92.0","draft":true,"prerelease":false}`) + test("prerelease", `{"tag_name":"v0.92.0","draft":false,"prerelease":true}`) } func TestFetchLatestRelease_HandlesNoReleases(t *testing.T) { diff --git a/services/nvpair-ui-broker/advertiser_test.go b/services/nvpair-ui-broker/advertiser_test.go index 1fe6d2f0..dd5b855f 100644 --- a/services/nvpair-ui-broker/advertiser_test.go +++ b/services/nvpair-ui-broker/advertiser_test.go @@ -33,23 +33,17 @@ func TestLocalEnginePort_UsesFallbackWhenStatusHasNoPort(t *testing.T) { } func TestRunningEnginePort_ReturnsPortOnlyForRunningEngine(t *testing.T) { - for _, tc := range []struct { - name string - raw string - port int - ok bool - }{ - {name: "running", raw: `{"running":true,"port":1235}`, port: 1235, ok: true}, - {name: "stopped is authoritative", raw: `{"running":false,"port":1235}`}, - {name: "running without a port", raw: `{"running":true,"port":0}`}, - {name: "malformed response", raw: `{`}, - } { - t.Run(tc.name, func(t *testing.T) { - port, ok := runningEnginePort([]byte(tc.raw)) - require.Equal(t, tc.port, port, "runningEnginePort") - require.Equal(t, tc.ok, ok, "runningEnginePort") + test := func(name, raw string, wantPort int, wantOK bool) { + t.Run(name, func(t *testing.T) { + port, ok := runningEnginePort([]byte(raw)) + require.Equal(t, wantPort, port) + require.Equal(t, wantOK, ok) }) } + test("running", `{"running":true,"port":1235}`, 1235, true) + test("stopped is authoritative", `{"running":false,"port":1235}`, 0, false) + test("running without a port", `{"running":true,"port":0}`, 0, false) + test("malformed response", `{`, 0, false) } func TestLMStudioFallback_NeverAdvertisesItsProxy(t *testing.T) { diff --git a/services/nvpair-ui-broker/discovery_test.go b/services/nvpair-ui-broker/discovery_test.go index 8b3cd290..39362b3c 100644 --- a/services/nvpair-ui-broker/discovery_test.go +++ b/services/nvpair-ui-broker/discovery_test.go @@ -122,33 +122,27 @@ func manualStatus(id, addr, uuid string) manualNodeStatus { // alias's address doesn't linger — and only the last removal releases the key. func TestManualAliases_KeepSharedKeyUntilLastAliasIsRemoved(t *testing.T) { const uuid = "shared-uuid" - for _, tc := range []struct { - name string - removeFirst string - survivorID string - survivorAddr string - }{ - {"remove-A-first", "alias-a", "alias-b", "10.0.0.2"}, - {"remove-B-first", "alias-b", "alias-a", "10.0.0.1"}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name, removeFirst, survivorID, survivorAddr string) { + t.Run(name, func(t *testing.T) { b := newManualTestBroker() b.upsertManualNode(manualStatus("alias-a", "10.0.0.1", uuid)) b.upsertManualNode(manualStatus("alias-b", "10.0.0.2", uuid)) require.Len(t, b.store.Snapshot(), 1, "two aliases for one machine should be one record") - b.removeManualNode(tc.removeFirst) + b.removeManualNode(removeFirst) snap := b.store.Snapshot() require.Len(t, snap, 1, "removing one of two aliases evicted the shared node") // The surviving alias must be reprojected: its id and address, not // the removed alias's stale payload. - require.Equal(t, tc.survivorID, snap[0].ID, "survivor not reprojected") - require.Equal(t, tc.survivorAddr, snap[0].IPAddress, "survivor not reprojected") + require.Equal(t, survivorID, snap[0].ID, "survivor not reprojected") + require.Equal(t, survivorAddr, snap[0].IPAddress, "survivor not reprojected") - b.removeManualNode(tc.survivorID) + b.removeManualNode(survivorID) require.Empty(t, b.store.Snapshot(), "record should be gone once the last alias left") }) } + test("remove A first", "alias-a", "alias-b", "10.0.0.2") + test("remove B first", "alias-b", "alias-a", "10.0.0.1") } // TestManualRekeyReprojectsSharedOldKey: when one of two aliases sharing a key diff --git a/services/nvpair-ui-broker/enginefacade_test.go b/services/nvpair-ui-broker/enginefacade_test.go index cdb48ed4..c0feae2f 100644 --- a/services/nvpair-ui-broker/enginefacade_test.go +++ b/services/nvpair-ui-broker/enginefacade_test.go @@ -78,25 +78,17 @@ func TestDefaultEngineFacade_RetriesAwayFromReservedPorts(t *testing.T) { func TestLlamaCPPFacadePreparation_PreservesConfiguredPorts(t *testing.T) { profile := mustEngineProxyProfile("llamacpp") - for _, tc := range []struct { - name string - serverPort int - proxyPort int - explicit bool - }{ - {name: "manifest default", serverPort: profile.EnginePortBase}, - {name: "explicit settings", serverPort: 18081, proxyPort: 18080, explicit: true}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, serverPort, proxyPort int, explicit bool) { + t.Run(name, func(t *testing.T) { b := &Broker{ proxyPath: "test-proxy", proxyEngines: []string{profile.Name}, engineSettingsLoaded: true, } - if tc.explicit { + if explicit { b.engineSettings = map[string]*engineSettingsRecord{ profile.Name: {Explicit: true, Snapshot: settings.Snapshot{ - Settings: settings.Config{ServerPort: tc.serverPort, ProxyPort: tc.proxyPort}, + Settings: settings.Config{ServerPort: serverPort, ProxyPort: proxyPort}, }}, } } @@ -110,7 +102,7 @@ func TestLlamaCPPFacadePreparation_PreservesConfiguredPorts(t *testing.T) { return } calls.Add(1) - if !assert.NoError(t, codec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: tc.serverPort}), "respond to unexpected engine request %s", msg.Method) { + if !assert.NoError(t, codec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: serverPort}), "respond to unexpected engine request %s", msg.Method) { return } } @@ -120,11 +112,13 @@ func TestLlamaCPPFacadePreparation_PreservesConfiguredPorts(t *testing.T) { assert.Equal(t, int32(0), calls.Load(), "preparation must not probe or relocate the engine") state := b.engineProxy(profile) - assert.Equal(t, int32(tc.serverPort), state.backendPort.Load(), "engine port") - assert.Equal(t, int32(tc.proxyPort), state.startupPort.Load(), "startup proxy port") - assert.Equal(t, tc.explicit, state.explicitSettings.Load(), "explicit settings") + assert.Equal(t, int32(serverPort), state.backendPort.Load(), "engine port") + assert.Equal(t, int32(proxyPort), state.startupPort.Load(), "startup proxy port") + assert.Equal(t, explicit, state.explicitSettings.Load(), "explicit settings") }) } + test("manifest default", profile.EnginePortBase, 0, false) + test("explicit settings", 18081, 18080, true) } func TestLlamaCPPProxyTerminalHandling_PreservesEngineState(t *testing.T) { diff --git a/services/nvpair-ui-broker/engineproxy_test.go b/services/nvpair-ui-broker/engineproxy_test.go index ee0382c6..15b02039 100644 --- a/services/nvpair-ui-broker/engineproxy_test.go +++ b/services/nvpair-ui-broker/engineproxy_test.go @@ -33,18 +33,16 @@ func TestEngineProxyTable_MatchesSharedEngines(t *testing.T) { // The probe path is the one advertiser value that is per-engine, and getting it // wrong makes a healthy engine look permanently down rather than failing loudly. func TestEngineHealthProbe_UsesEngineSpecificEndpoint(t *testing.T) { - for _, tc := range []struct { - engine string - want string - }{ - {"ollama", "/"}, - {"lmstudio", "/v1/models"}, - {"llamacpp", "/health"}, - } { - p, ok := engineProxyProfileFor(tc.engine) - require.True(t, ok, "no profile for") - assert.Equal(t, tc.want, p.HealthProbePath) + test := func(engine, want string) { + t.Run(engine, func(t *testing.T) { + p, ok := engineProxyProfileFor(engine) + require.True(t, ok, "no profile for engine") + assert.Equal(t, want, p.HealthProbePath) + }) } + test("ollama", "/") + test("lmstudio", "/v1/models") + test("llamacpp", "/health") } // proxyport.go and lmstudioport.go restate ports and error-ID prefixes the @@ -52,23 +50,17 @@ func TestEngineHealthProbe_UsesEngineSpecificEndpoint(t *testing.T) { // package. They are deleted when those files collapse; until then this is what // stops them drifting from the table. func TestBrokerConstants_MatchCanonicalEngineTable(t *testing.T) { - for _, tc := range []struct { - engine string - facade int - backendStart int - blockedID string - }{ - {"ollama", managedOllamaFacadePort, managedOllamaBackendStart, portOwnershipBlockedID}, - {"lmstudio", managedLMStudioFacadePort, managedLMStudioBackendStart, lmstudioPortOwnershipBlockedID}, - } { - t.Run(tc.engine, func(t *testing.T) { - p, ok := engineProxyProfileFor(tc.engine) - require.True(t, ok, "no profile for") - assert.Equal(t, tc.facade, p.FacadePort, "facade constant") - assert.Equal(t, tc.backendStart, p.EnginePortBase, "backend-start constant") - assert.Equal(t, p.ComponentName()+":port-ownership-blocked", tc.blockedID, "blocked error id") + test := func(engine string, facade, backendStart int, blockedID string) { + t.Run(engine, func(t *testing.T) { + p, ok := engineProxyProfileFor(engine) + require.True(t, ok, "no profile for engine") + assert.Equal(t, facade, p.FacadePort, "facade constant") + assert.Equal(t, backendStart, p.EnginePortBase, "backend-start constant") + assert.Equal(t, p.ComponentName()+":port-ownership-blocked", blockedID, "blocked error id") }) } + test("ollama", managedOllamaFacadePort, managedOllamaBackendStart, portOwnershipBlockedID) + test("lmstudio", managedLMStudioFacadePort, managedLMStudioBackendStart, lmstudioPortOwnershipBlockedID) assert.Equal(t, ollamaProxyProfile.ComponentName()+":port-bumped", proxyPortBumpedID, "bumped error id") } @@ -76,18 +68,16 @@ func TestBrokerConstants_MatchCanonicalEngineTable(t *testing.T) { // backwards does not fail // to compile — it silently changes which engine the broker believes it may stop. func TestEngineOwnership_AssignsManagedAndExternalListeners(t *testing.T) { - for _, tc := range []struct { - engine string - want engineOwnership - }{ - {"ollama", adoptedEngine}, - {"lmstudio", managedEngine}, - {"llamacpp", managedEngine}, - } { - p, ok := engineProxyProfileFor(tc.engine) - require.True(t, ok, "no profile for") - assert.Equal(t, tc.want, p.Ownership) + test := func(engine string, want engineOwnership) { + t.Run(engine, func(t *testing.T) { + p, ok := engineProxyProfileFor(engine) + require.True(t, ok, "no profile for engine") + assert.Equal(t, want, p.Ownership) + }) } + test("ollama", adoptedEngine) + test("lmstudio", managedEngine) + test("llamacpp", managedEngine) } // The two engines' port choreography diverges on exactly one input, and this is @@ -102,17 +92,10 @@ func TestEngineOwnership_AssignsManagedAndExternalListeners(t *testing.T) { // This is the regression guard for collapsing the two planners into one. A // change that makes both engines agree here has broken one of them. func TestOwnership_DecidesTheOccupiedFacadeOutcome(t *testing.T) { - for _, tc := range []struct { - engine string - wantMove bool - wantBlock string - }{ - {engine: "ollama", wantBlock: "Ollama is already running on the compatibility port"}, - {engine: "lmstudio", wantMove: true}, - } { - t.Run(tc.engine, func(t *testing.T) { - p, ok := engineProxyProfileFor(tc.engine) - require.True(t, ok, "no profile for") + test := func(engine string, wantMove bool, wantBlock string) { + t.Run(engine, func(t *testing.T) { + p, ok := engineProxyProfileFor(engine) + require.True(t, ok, "no profile for engine") // Running on the facade, which is therefore taken; the backend base // is free. status := ollamaPortStatus{Running: true, Port: p.FacadePort} @@ -120,42 +103,37 @@ func TestOwnership_DecidesTheOccupiedFacadeOutcome(t *testing.T) { got := planManagedEnginePorts(p, true, status, available) - if tc.wantMove { + if wantMove { require.Equal(t, managedPortPlan{Enabled: true, BackendPort: p.EnginePortBase}, got, "a managed engine must be moved, not refused") return } - require.Equal(t, tc.wantBlock, got.Blocked, "plan (%v)", got) + require.Equal(t, wantBlock, got.Blocked, "plan (%v)", got) }) } + test("ollama", false, "Ollama is already running on the compatibility port") + test("lmstudio", true, "") } // --proxy-engines selects which engines the one binary is started for. func TestParseProxyEngines_ValidatesSelectedEngineNames(t *testing.T) { - for _, tc := range []struct { - name string - csv string - want []string - wantErr bool - }{ - {name: "default set", csv: "ollama,lmstudio,llamacpp", want: []string{"ollama", "lmstudio", "llamacpp"}}, - {name: "single engine", csv: "lmstudio", want: []string{"lmstudio"}}, - {name: "whitespace and blanks are tolerated", csv: " ollama , , lmstudio ", want: []string{"ollama", "lmstudio"}}, - {name: "duplicates collapse", csv: "ollama,ollama", want: []string{"ollama"}}, - {name: "empty selects nothing", csv: "", want: nil}, - // Silently fronting the engines it did recognize would look like the - // flag worked. - {name: "unknown engine fails", csv: "ollama,vllm", wantErr: true}, - } { - t.Run(tc.name, func(t *testing.T) { - got, err := parseProxyEngines(tc.csv) - if tc.wantErr { + test := func(name, csv string, want []string, wantErr bool) { + t.Run(name, func(t *testing.T) { + got, err := parseProxyEngines(csv) + if wantErr { require.Error(t, err, "parseProxyEngines (%v)", got) return } require.NoError(t, err, "parseProxyEngines") - require.Equal(t, append([]string{}, tc.want...), append([]string{}, got...), "parseProxyEngines") + require.Equal(t, append([]string{}, want...), append([]string{}, got...)) }) } + test("default set", "ollama,lmstudio,llamacpp", []string{"ollama", "lmstudio", "llamacpp"}, false) + test("single engine", "lmstudio", []string{"lmstudio"}, false) + test("whitespace and blanks are tolerated", " ollama , , lmstudio ", []string{"ollama", "lmstudio"}, false) + test("duplicates collapse", "ollama,ollama", []string{"ollama"}, false) + test("empty selects nothing", "", nil, false) + // Silently fronting the engines it did recognize would look like the flag worked. + test("unknown engine fails", "ollama,vllm", nil, true) } // An engine left out of --proxy-engines is not started, and neither is any diff --git a/services/nvpair-ui-broker/enginesettings_security_test.go b/services/nvpair-ui-broker/enginesettings_security_test.go index 94a44132..0bdd4ff2 100644 --- a/services/nvpair-ui-broker/enginesettings_security_test.go +++ b/services/nvpair-ui-broker/enginesettings_security_test.go @@ -37,15 +37,19 @@ func TestSettings_DerivesCORSGuardFromAuthenticatedCaller(t *testing.T) { } { require.NoError(t, os.WriteFile(filepath.Join(h.b.clusterDir, name), data, 0600)) } - for _, caller := range []string{"", "paired-caller"} { - request := h.request(t) - // Deliberately supply the opposite of what the authenticated caller needs. - request.PreserveCORS = caller == "" - _, err := h.b.previewEngineSettings(context.Background(), request, caller) - require.NoError(t, err) - require.Equal(t, caller != "", h.previewPreserveCORS.Load(), "preview trusted client-supplied guard") - _, err = h.b.applyEngineSettings(context.Background(), request, caller) - require.NoError(t, err) - require.Equal(t, caller != "", h.previewPreserveCORS.Load(), "apply trusted client-supplied guard") + test := func(name, caller string) { + t.Run(name, func(t *testing.T) { + request := h.request(t) + // Deliberately supply the opposite of what the authenticated caller needs. + request.PreserveCORS = caller == "" + _, err := h.b.previewEngineSettings(context.Background(), request, caller) + require.NoError(t, err) + require.Equal(t, caller != "", h.previewPreserveCORS.Load(), "preview trusted client-supplied guard") + _, err = h.b.applyEngineSettings(context.Background(), request, caller) + require.NoError(t, err) + require.Equal(t, caller != "", h.previewPreserveCORS.Load(), "apply trusted client-supplied guard") + }) } + test("local caller", "") + test("authenticated peer", "paired-caller") } diff --git a/services/nvpair-ui-broker/enginesettings_test.go b/services/nvpair-ui-broker/enginesettings_test.go index fbc84296..4bb0c250 100644 --- a/services/nvpair-ui-broker/enginesettings_test.go +++ b/services/nvpair-ui-broker/enginesettings_test.go @@ -235,31 +235,22 @@ func TestSettingsCoordinator_TracksRevisionsDeduplicatesAndReportsFailures(t *te } func TestSettingsFailedApply_RestoresOnlyRunningReadyService(t *testing.T) { - for _, tc := range []struct { - name string - stopFails bool - proxyReady bool - advertised bool - }{ - {"stop failed with engine still running", true, true, true}, - {"engine stopped before startup failed", false, true, false}, - {"proxy unavailable", true, false, false}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, stopFails, proxyReady, advertised bool) { + t.Run(name, func(t *testing.T) { h := newSettingsHarness(t) p := h.request(t) oldPort := p.Settings.ProxyPort h.b.regCache = relay.NewRegistrationCache() h.b.registerService(noderec.RegisterParams{Service: noderec.ServiceOllama, Port: oldPort}) - h.failBeforeStop.Store(tc.stopFails) + h.failBeforeStop.Store(stopFails) h.fail.Store(true) - h.loseProxyOnStop.Store(!tc.proxyReady) + h.loseProxyOnStop.Store(!proxyReady) p.Settings.LaunchText += " --new-option" receipt, err := h.b.applyEngineSettings(context.Background(), p, "") require.NoError(t, err, "expected failed receipt (%v)", receipt) require.Equal(t, "failed", receipt.Phase, "expected failed receipt (%v)", receipt) registered := h.b.regCache.Snapshot() - if tc.advertised { + if advertised { require.Len(t, registered, 1, "running engine lost registration") require.Equal(t, noderec.ServiceOllama, registered[0].Service, "running engine lost registration (%v)", registered) require.Equal(t, oldPort, registered[0].Port, "running engine lost registration (%v)", registered) @@ -268,6 +259,9 @@ func TestSettingsFailedApply_RestoresOnlyRunningReadyService(t *testing.T) { } }) } + test("stop failed with engine still running", true, true, true) + test("engine stopped before startup failed", false, true, false) + test("proxy unavailable", true, false, false) } func TestSettingsCoordinator_SerializesAndCancelsQueuedRequests(t *testing.T) { diff --git a/services/nvpair-ui-broker/lmstudioport_test.go b/services/nvpair-ui-broker/lmstudioport_test.go index 7fd55bc6..d53a47fe 100644 --- a/services/nvpair-ui-broker/lmstudioport_test.go +++ b/services/nvpair-ui-broker/lmstudioport_test.go @@ -38,26 +38,18 @@ func TestPlanManagedLMStudioPorts_AvoidsFacadeAndEnginePortConflicts(t *testing. } return func(port int) bool { return set[port] } } - tests := []struct { - name string - on bool - st ollamaPortStatus - free func(int) bool - want managedPortPlan - }{ - {"disabled", false, ollamaPortStatus{Port: 1234}, free(1234, 1235), managedPortPlan{}}, - {"stopped default moves", true, ollamaPortStatus{Port: 1234}, free(1234, 1235), managedPortPlan{Enabled: true, BackendPort: 1235}}, - {"running identified default moves", true, ollamaPortStatus{Running: true, Port: 1234}, free(1235), managedPortPlan{Enabled: true, BackendPort: 1235}}, - {"occupied stopped backend advances", true, ollamaPortStatus{Port: 1235}, free(1234, 1236), managedPortPlan{Enabled: true, BackendPort: 1236}}, - {"running backend is preserved", true, ollamaPortStatus{Running: true, Port: 1235}, free(1234, 1236), managedPortPlan{Enabled: true}}, - {"custom backend preserved", true, ollamaPortStatus{Running: true, Port: 12400}, free(1234), managedPortPlan{Enabled: true}}, - {"unknown facade owner blocks", true, ollamaPortStatus{Port: 1235}, free(1235), managedPortPlan{Blocked: "the compatibility port is already in use"}}, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.want, planManagedLMStudioPorts(tc.on, tc.st, tc.free), "planManagedLMStudioPorts") + test := func(name string, on bool, st ollamaPortStatus, available func(int) bool, want managedPortPlan) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, planManagedLMStudioPorts(on, st, available)) }) } + test("disabled", false, ollamaPortStatus{Port: 1234}, free(1234, 1235), managedPortPlan{}) + test("stopped default moves", true, ollamaPortStatus{Port: 1234}, free(1234, 1235), managedPortPlan{Enabled: true, BackendPort: 1235}) + test("running identified default moves", true, ollamaPortStatus{Running: true, Port: 1234}, free(1235), managedPortPlan{Enabled: true, BackendPort: 1235}) + test("occupied stopped backend advances", true, ollamaPortStatus{Port: 1235}, free(1234, 1236), managedPortPlan{Enabled: true, BackendPort: 1236}) + test("running backend is preserved", true, ollamaPortStatus{Running: true, Port: 1235}, free(1234, 1236), managedPortPlan{Enabled: true}) + test("custom backend preserved", true, ollamaPortStatus{Running: true, Port: 12400}, free(1234), managedPortPlan{Enabled: true}) + test("unknown facade owner blocks", true, ollamaPortStatus{Port: 1235}, free(1235), managedPortPlan{Blocked: "the compatibility port is already in use"}) } // The engine, its port, and the ignore-persisted decision travel in @@ -239,17 +231,8 @@ func TestLMStudioReady_FallsBackAndWarnsForUnexpectedPort(t *testing.T) { } func TestLMStudioRequests_WaitForPortGate(t *testing.T) { - tests := []struct { - method string - params string - }{ - {"engine:get-installed", `{}`}, - {"engine:status", `{"engine":"lmstudio"}`}, - {"engine:start", `{"engine":"lmstudio"}`}, - {"engine:restart", `{"engine":"lmstudio"}`}, - } - for _, tc := range tests { - t.Run(tc.method, func(t *testing.T) { + test := func(name, requestMethod, params string) { + t.Run(name, func(t *testing.T) { engineClient, engineServer := net.Pipe() brokerClient, brokerServer := net.Pipe() t.Cleanup(func() { @@ -288,8 +271,8 @@ func TestLMStudioRequests_WaitForPortGate(t *testing.T) { b.relayToEngine(&Message{ JSONRPC: "2.0", ID: &id, - Method: tc.method, - Params: json.RawMessage(tc.params), + Method: requestMethod, + Params: json.RawMessage(params), }) select { @@ -300,9 +283,9 @@ func TestLMStudioRequests_WaitForPortGate(t *testing.T) { close(b.lmstudioPortReady) select { case got := <-method: - require.Equal(t, tc.method, got, "relayed method") + require.Equal(t, requestMethod, got, "relayed method") case <-time.After(2 * time.Second): - require.FailNow(t, fmt.Sprintf("%q was not relayed after the LM Studio port gate opened", tc.method)) + require.FailNow(t, "request was not relayed after the LM Studio port gate opened") } select { case err := <-response: @@ -312,29 +295,29 @@ func TestLMStudioRequests_WaitForPortGate(t *testing.T) { } }) } + test("inventory", "engine:get-installed", `{}`) + test("status", "engine:status", `{"engine":"lmstudio"}`) + test("start", "engine:start", `{"engine":"lmstudio"}`) + test("restart", "engine:restart", `{"engine":"lmstudio"}`) } func TestLMStudioPortGate_MatchesRequestsThatRequireStablePorts(t *testing.T) { - for _, tc := range []struct { - method string - params string - want bool - }{ - {"engine:get-installed", `{}`, true}, - {"engine:status", `{"engine":"lmstudio"}`, true}, - {"engine:start", `{"engine":"lmstudio"}`, true}, - {"engine:restart", `{"engine":"lmstudio"}`, true}, - // Gated for the same reason Ollama gates it: an install that starts - // the engine assigns a port, and mid-transition that can be the port - // the proxy is taking. - {"engine:install", `{"engine":"lmstudio"}`, true}, - {"engine:install", `{"engine":"ollama"}`, false}, - {"engine:status", `{"engine":"ollama"}`, false}, - {"engine:models", `{"engine":"lmstudio"}`, false}, - {"engine:status", `{`, false}, - } { - assert.Equal(t, tc.want, needsLMStudioPortGate(tc.method, json.RawMessage(tc.params)), "needsLMStudioPortGate(%q, %s)", tc.method, tc.params) + test := func(name, method, params string, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, needsLMStudioPortGate(method, json.RawMessage(params))) + }) } + test("inventory waits", "engine:get-installed", `{}`, true) + test("LM Studio status waits", "engine:status", `{"engine":"lmstudio"}`, true) + test("LM Studio start waits", "engine:start", `{"engine":"lmstudio"}`, true) + test("LM Studio restart waits", "engine:restart", `{"engine":"lmstudio"}`, true) + // An install that starts the engine assigns a port, and mid-transition + // that can be the port the proxy is taking. + test("LM Studio install waits", "engine:install", `{"engine":"lmstudio"}`, true) + test("Ollama install does not wait", "engine:install", `{"engine":"ollama"}`, false) + test("Ollama status does not wait", "engine:status", `{"engine":"ollama"}`, false) + test("LM Studio models do not wait", "engine:models", `{"engine":"lmstudio"}`, false) + test("malformed parameters do not wait", "engine:status", `{`, false) } func TestManagedLMStudioPortGate_HonorsCancellation(t *testing.T) { diff --git a/services/nvpair-ui-broker/nodeactivity_test.go b/services/nvpair-ui-broker/nodeactivity_test.go index d0c5237c..297dfe90 100644 --- a/services/nvpair-ui-broker/nodeactivity_test.go +++ b/services/nvpair-ui-broker/nodeactivity_test.go @@ -142,22 +142,16 @@ func TestRelayedActivityAge_AccumulatesTransitDelay(t *testing.T) { // places the observation in the future — and a future observation never expires, // so the node could never be probed again. func TestReportedAge_IsClampedAtBothEnds(t *testing.T) { - for _, tc := range []struct { - name string - msSince int64 - want time.Duration - }{ - {"a normal age passes through", 4_000, 4 * time.Second}, - {"negative floors at zero", -60_000, 0}, - {"absurd caps at the ceiling", 1 << 62, activityAgeCeiling}, - {"the overflow boundary caps too", int64(1) << 53, activityAgeCeiling}, - } { - t.Run(tc.name, func(t *testing.T) { - got := clampActivityAge(tc.msSince) - require.Equal(t, tc.want, got, "clampActivityAge") - require.GreaterOrEqual(t, got, time.Duration(0), "clampActivityAge") + test := func(name string, msSince int64, want time.Duration) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, clampActivityAge(msSince)) + require.GreaterOrEqual(t, clampActivityAge(msSince), time.Duration(0)) }) } + test("a normal age passes through", 4_000, 4*time.Second) + test("negative floors at zero", -60_000, 0) + test("absurd caps at the ceiling", 1<<62, activityAgeCeiling) + test("the overflow boundary caps too", int64(1)<<53, activityAgeCeiling) } // An unidentified peer cannot be vouched for: relaying it would credit whichever diff --git a/services/nvpair-ui-broker/nodeinfo_identity_test.go b/services/nvpair-ui-broker/nodeinfo_identity_test.go index f7548182..a5ca2e01 100644 --- a/services/nvpair-ui-broker/nodeinfo_identity_test.go +++ b/services/nvpair-ui-broker/nodeinfo_identity_test.go @@ -21,19 +21,11 @@ import ( // a renamed method silently stops membership reaching it — and the only symptom // would be peers going on suppressing an invite for a node that has left. func TestWriteClusterIdentityFrame_EmitsNewlineTerminatedNotification(t *testing.T) { - for _, tc := range []struct { - name string - clusterUUID string - }{ - {"a principal", "our-principal"}, - // A departure is the value peers are waiting for, so it is sent like any - // other rather than skipped as empty. - {"a departure", ""}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name, clusterUUID string) { + t.Run(name, func(t *testing.T) { var buf bytes.Buffer var mu sync.Mutex - require.NoError(t, writeClusterIdentityFrame(&mu, &buf, tc.clusterUUID), "write") + require.NoError(t, writeClusterIdentityFrame(&mu, &buf, clusterUUID), "write") line := buf.String() assert.True(t, strings.HasSuffix(line, "\n"), "frame is not newline-terminated; node-info reads line-delimited frames") @@ -51,10 +43,14 @@ func TestWriteClusterIdentityFrame_EmitsNewlineTerminatedNotification(t *testing // A notification, not a request: node-info's stdout is drained to // io.Discard, so an id-bearing frame would strand a reply. assert.Empty(t, frame.ID, "the push must be a notification") - assert.Equal(t, tc.clusterUUID, frame.Params.ClusterUUID, "clusterUuid") + assert.Equal(t, clusterUUID, frame.Params.ClusterUUID, "clusterUuid") // The field must be on the wire even when empty, since that is how a // departure is expressed. assert.Contains(t, line, `"clusterUuid"`, "frame omitted clusterUuid") }) } + test("a principal", "our-principal") + // A departure is the value peers are waiting for, so it is sent like any + // other rather than skipped as empty. + test("a departure", "") } diff --git a/services/nvpair-ui-broker/nodeinfo_observed_test.go b/services/nvpair-ui-broker/nodeinfo_observed_test.go index d5d28897..1e8bea32 100644 --- a/services/nvpair-ui-broker/nodeinfo_observed_test.go +++ b/services/nvpair-ui-broker/nodeinfo_observed_test.go @@ -21,18 +21,8 @@ import ( // the notification leaves address ranking with no peer evidence at all while every // component's own unit tests still pass. func TestForwardNodeInfoObservedAddresses_ReachesTheScanner(t *testing.T) { - for _, tc := range []struct { - name string - from string - addrs []string - }{ - {"a reported set", `{"addresses":["10.172.54.70","10.0.0.5"]}`, []string{"10.172.54.70", "10.0.0.5"}}, - // An empty set is a withdrawal, not a no-op: it retires evidence whose TTL - // expired, and suppressing it would leave the scanner ranking on a link - // that has gone away. - {"a withdrawal", `{"addresses":[]}`, []string{}}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name, from string, addrs []string) { + t.Run(name, func(t *testing.T) { brokerSide, scannerSide := net.Pipe() t.Cleanup(func() { _ = brokerSide.Close() @@ -55,17 +45,22 @@ func TestForwardNodeInfoObservedAddresses_ReachesTheScanner(t *testing.T) { _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) }() - b.forwardNodeInfoNotification(noderec.NotifyObservedAddresses, json.RawMessage(tc.from)) + b.forwardNodeInfoNotification(noderec.NotifyObservedAddresses, json.RawMessage(from)) select { case msg := <-relayed: require.Equal(t, noderec.MethodSetObservedAddresses, msg.Method, "relayed method") var got noderec.ObservedAddressesParams require.NoError(t, json.Unmarshal(msg.Params, &got), "decode relayed params") - require.Equal(t, tc.addrs, append([]string{}, got.Addresses...), "relayed addresses") + require.Equal(t, addrs, append([]string{}, got.Addresses...), "relayed addresses") case <-time.After(2 * time.Second): require.FailNow(t, "observed addresses never reached the scanner") } }) } + test("a reported set", `{"addresses":["10.172.54.70","10.0.0.5"]}`, []string{"10.172.54.70", "10.0.0.5"}) + // An empty set is a withdrawal, not a no-op: it retires evidence whose TTL + // expired, and suppressing it would leave the scanner ranking on a link + // that has gone away. + test("a withdrawal", `{"addresses":[]}`, []string{}) } diff --git a/services/nvpair-ui-broker/ollamahost_test.go b/services/nvpair-ui-broker/ollamahost_test.go index 6d79ca2b..52f23bba 100644 --- a/services/nvpair-ui-broker/ollamahost_test.go +++ b/services/nvpair-ui-broker/ollamahost_test.go @@ -161,61 +161,40 @@ func TestConfiguredEngineProxyPort_UsesSelectedEngineFacadePort(t *testing.T) { // Both directions are checked because the two engines have separate fallback // functions and fixing one is not evidence about the other. func TestProxyFallback_SkipsASiblingEnginesConfiguredPort(t *testing.T) { - for _, tc := range []struct { - name string - self engineProxyProfile - sibling engineProxyProfile - fallback func(*Broker, ...int) int - }{ - { - name: "ollama fallback avoids LM Studio", - self: ollamaProxyProfile, - sibling: lmstudioProxyProfile, - fallback: (*Broker).setOllamaProxyFallback, - }, - { - name: "lmstudio fallback avoids Ollama", - self: lmstudioProxyProfile, - sibling: ollamaProxyProfile, - fallback: (*Broker).setLMStudioProxyFallback, - }, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, self, sibling engineProxyProfile, fallback func(*Broker, ...int) int) { + t.Run(name, func(t *testing.T) { isolateOllamaHostTestConfig(t) // Put the sibling's persisted port inside the search range so a // fallback that ignores siblings would hand it out. - stored := tc.self.EnginePortBase + 1 - path, err := appdir.Path(tc.sibling.PortFile) + stored := self.EnginePortBase + 1 + path, err := appdir.Path(sibling.PortFile) require.NoError(t, err) require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755)) require.NoError(t, os.WriteFile(path, fmt.Appendf(nil, `{"port":%d}`, stored), 0o600)) b := &Broker{} - got := tc.fallback(b, tc.self.EnginePortBase) - require.NotEqual(t, stored, got, "fallback must skip %s's configured port", tc.sibling.DisplayName) - require.NotEqual(t, tc.sibling.FacadePort, got, "fallback must skip %s's default ports", tc.sibling.DisplayName) - require.NotEqual(t, tc.sibling.EnginePortBase, got, "fallback must skip %s's default ports", tc.sibling.DisplayName) + got := fallback(b, self.EnginePortBase) + require.NotEqual(t, stored, got, "fallback must skip the sibling's configured port") + require.NotEqual(t, sibling.FacadePort, got, "fallback must skip the sibling's default facade port") + require.NotEqual(t, sibling.EnginePortBase, got, "fallback must skip the sibling's default engine port") }) } + test("ollama fallback avoids LM Studio", ollamaProxyProfile, lmstudioProxyProfile, (*Broker).setOllamaProxyFallback) + test("lmstudio fallback avoids Ollama", lmstudioProxyProfile, ollamaProxyProfile, (*Broker).setLMStudioProxyFallback) } func TestPrepareOllamaHostAlias_HonorsOptOutAndBackendOwnership(t *testing.T) { t.Setenv("OLLAMA_HOST", "127.0.0.1:11433") - for _, tc := range []struct { - name string - enabled bool - backendPort int - }{ - {name: "force ports opt out", enabled: false, backendPort: 11434}, - {name: "custom backend owns alias port", enabled: true, backendPort: 11433}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, enabled bool, backendPort int) { + t.Run(name, func(t *testing.T) { b := &Broker{} - b.prepareOllamaHostAlias(tc.enabled, tc.backendPort) + b.prepareOllamaHostAlias(enabled, backendPort) require.Equal(t, ollamaHostAlias{}, b.currentOllamaHostAlias(), "unsafe alias prepared") }) } + test("force ports opt out", false, 11434) + test("custom backend owns alias port", true, 11433) } func TestAliasWarning_ReplaysAfterErrorsProcessRecovery(t *testing.T) { @@ -253,31 +232,9 @@ func TestAliasWarning_ReplaysAfterErrorsProcessRecovery(t *testing.T) { // prepared, so a plain TCP probe still reports it free — it must be treated as // taken or the advancing backend would land on it and orphan the alias. func TestManagedBackendMoves_SkipReservedOllamaHostAlias(t *testing.T) { - for _, tc := range []struct { - name string - facadePort int - backendStart int - plan func(status ollamaPortStatus, available func(int) bool) managedPortPlan - }{ - { - name: "ollama", - facadePort: managedOllamaFacadePort, - backendStart: managedOllamaBackendStart, - plan: func(status ollamaPortStatus, available func(int) bool) managedPortPlan { - return planManagedOllamaPorts(true, status, available) - }, - }, - { - name: "lmstudio", - facadePort: managedLMStudioFacadePort, - backendStart: managedLMStudioBackendStart, - plan: func(status ollamaPortStatus, available func(int) bool) managedPortPlan { - return planManagedLMStudioPorts(true, status, available) - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - aliasPort := tc.backendStart + 1 + test := func(name string, facadePort, backendStart int, plan func(bool, ollamaPortStatus, func(int) bool) managedPortPlan) { + t.Run(name, func(t *testing.T) { + aliasPort := backendStart + 1 want := aliasPort + 1 b := &Broker{} b.setOllamaHostAlias(ollamaHostAlias{Port: aliasPort}) @@ -285,12 +242,14 @@ func TestManagedBackendMoves_SkipReservedOllamaHostAlias(t *testing.T) { // the alias and the port after it are free above it, and the proxy has // not bound the alias yet, so a plain probe still reports it free. available := b.availableOffOllamaHostAlias(func(port int) bool { - return port == tc.facadePort || port == aliasPort || port == want + return port == facadePort || port == aliasPort || port == want }) - got := tc.plan(ollamaPortStatus{Port: tc.backendStart}, available) + got := plan(true, ollamaPortStatus{Port: backendStart}, available) require.Equal(t, want, got.BackendPort, "the alias must be skipped") }) } + test("ollama", managedOllamaFacadePort, managedOllamaBackendStart, planManagedOllamaPorts) + test("lmstudio", managedLMStudioFacadePort, managedLMStudioBackendStart, planManagedLMStudioPorts) // Without an alias the wrapper must be transparent. plain := (&Broker{}).availableOffOllamaHostAlias(func(port int) bool { return port == managedOllamaBackendStart }) diff --git a/services/nvpair-ui-broker/proxyaddressing_test.go b/services/nvpair-ui-broker/proxyaddressing_test.go index bc90d925..f5efafa2 100644 --- a/services/nvpair-ui-broker/proxyaddressing_test.go +++ b/services/nvpair-ui-broker/proxyaddressing_test.go @@ -145,39 +145,18 @@ func TestFacadeEnable_TrustsReadinessOnlyWhenRequestWasUnanswered(t *testing.T) ready := &proxyProcess{facadeState: readyFacade(engine, 11434)} notReady := &proxyProcess{} - for _, tc := range []struct { - name string - pp *proxyProcess - err error - want bool - }{ - { - // A timeout or closed pipe: the child does the bind, two notifies - // and the serve inside the call, so it may genuinely be serving. - name: "unanswered enable, facade reported ready", - pp: ready, err: context.DeadlineExceeded, want: true, - }, - { - name: "unanswered enable, facade never reported ready", - pp: notReady, err: context.DeadlineExceeded, want: false, - }, - { - // The child answered, so it decided against the facade and - // withdrew it: an earlier ready from the same attempt is stale. - name: "answered rejection, stale ready ignored", - pp: ready, err: fmt.Errorf("%w: rejected", errRPCAnswered), want: false, - }, - { - name: "answered bind failure, stale ready ignored", - pp: ready, - err: fmt.Errorf("%w: %w: taken", errRPCAnswered, errFacadeBindFailed), - want: false, - }, - } { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.want, facadeCameUpAnyway(tc.pp, engine, tc.err), "facadeCameUpAnyway") + test := func(name string, pp *proxyProcess, err error, want bool) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, facadeCameUpAnyway(pp, engine, err)) }) } + // The child does the bind, notifications and serve inside an unanswered call, + // so it may genuinely be serving despite a timeout or closed pipe. + test("unanswered enable, facade reported ready", ready, context.DeadlineExceeded, true) + test("unanswered enable, facade never reported ready", notReady, context.DeadlineExceeded, false) + // An answered rejection withdraws the facade, making an earlier ready stale. + test("answered rejection, stale ready ignored", ready, fmt.Errorf("%w: rejected", errRPCAnswered), false) + test("answered bind failure, stale ready ignored", ready, fmt.Errorf("%w: %w: taken", errRPCAnswered, errFacadeBindFailed), false) } // readyFacade is the handle state one engine's facade would leave behind after @@ -391,60 +370,21 @@ func TestResubscribe_ReplacesOnlyThatEnginesSubscription(t *testing.T) { // names, so it has to see through the address. An unaddressed notification is // process-scoped and passes through untouched. func TestFacadeMethodFor_StripsOnlyItsOwnEngine(t *testing.T) { - for _, tc := range []struct { - name string - profile engineProxyProfile - method string - want string - ok bool - }{ - { - name: "own address is stripped", - profile: ollamaProxyProfile, - method: ollamaProxyProfile.addressed("ready"), - want: "ready", - ok: true, - }, - { - // forwardProxyProcessNotification routes by engine before this - // reader sees anything, so reaching it with another engine's - // address means addressing broke. Forwarding it would file one - // engine's event under another. - name: "another engine's address is dropped", - profile: ollamaProxyProfile, - method: lmstudioProxyProfile.addressed("ready"), - ok: false, - }, - { - name: "process-scoped method passes through", - profile: ollamaProxyProfile, - method: "workload:started", - want: "workload:started", - ok: true, - }, - { - // The errors relay matches bare names, so an addressed error has to - // come out as errors:report and not as ollama:errors:report. - name: "addressed error becomes a bare errors method", - profile: ollamaProxyProfile, - method: ollamaProxyProfile.addressed(methodErrorsReport), - want: methodErrorsReport, - ok: true, - }, - { - name: "unaddressed error is untouched", - profile: lmstudioProxyProfile, - method: methodErrorsReport, - want: methodErrorsReport, - ok: true, - }, - } { - t.Run(tc.name, func(t *testing.T) { - got, ok := facadeMethodFor(tc.profile, tc.method) - require.Equal(t, tc.ok, ok, "facadeMethodFor") + test := func(name string, profile engineProxyProfile, method, want string, wantOK bool) { + t.Run(name, func(t *testing.T) { + got, ok := facadeMethodFor(profile, method) + require.Equal(t, wantOK, ok) if ok { - require.Equal(t, tc.want, got, "facadeMethodFor") + require.Equal(t, want, got) } }) } + test("own address is stripped", ollamaProxyProfile, ollamaProxyProfile.addressed("ready"), "ready", true) + // Another engine's address means routing broke: forwarding would file one + // engine's event under another. + test("another engine's address is dropped", ollamaProxyProfile, lmstudioProxyProfile.addressed("ready"), "", false) + test("process-scoped method passes through", ollamaProxyProfile, "workload:started", "workload:started", true) + // The errors relay matches bare names, so an addressed error must come out bare. + test("addressed error becomes a bare errors method", ollamaProxyProfile, ollamaProxyProfile.addressed(methodErrorsReport), methodErrorsReport, true) + test("unaddressed error is untouched", lmstudioProxyProfile, methodErrorsReport, methodErrorsReport, true) } diff --git a/services/nvpair-ui-broker/proxyport_test.go b/services/nvpair-ui-broker/proxyport_test.go index 1d8fb38f..652f0beb 100644 --- a/services/nvpair-ui-broker/proxyport_test.go +++ b/services/nvpair-ui-broker/proxyport_test.go @@ -68,71 +68,27 @@ func TestPlanManagedOllamaPorts_AvoidsFacadeAndEnginePortConflicts(t *testing.T) return func(port int) bool { return set[port] } } - for _, tc := range []struct { - name string - enabled bool - status ollamaPortStatus - available func(int) bool - want managedPortPlan - }{ - { - name: "policy disabled changes nothing", - available: free(managedOllamaFacadePort, managedOllamaBackendStart), - }, - { - name: "stopped default engine moves behind facade", - enabled: true, - status: ollamaPortStatus{Port: managedOllamaFacadePort}, - available: free(managedOllamaFacadePort, managedOllamaBackendStart), - want: managedPortPlan{Enabled: true, BackendPort: managedOllamaBackendStart}, - }, - { - name: "allocator skips occupied preferred backend", - enabled: true, - status: ollamaPortStatus{Port: managedOllamaFacadePort}, - available: free(managedOllamaFacadePort, managedOllamaBackendStart+1), - want: managedPortPlan{Enabled: true, BackendPort: managedOllamaBackendStart + 1}, - }, - { - name: "occupied stopped backend advances", - enabled: true, - status: ollamaPortStatus{Port: managedOllamaBackendStart}, - available: free(managedOllamaFacadePort, managedOllamaBackendStart+1), - want: managedPortPlan{Enabled: true, BackendPort: managedOllamaBackendStart + 1}, - }, - { - name: "running backend is preserved", - enabled: true, - status: ollamaPortStatus{Running: true, Port: managedOllamaBackendStart}, - available: free(managedOllamaFacadePort, managedOllamaBackendStart+1), - want: managedPortPlan{Enabled: true}, - }, - { - name: "custom backend is preserved", - enabled: true, - status: ollamaPortStatus{Running: true, Port: 12000}, - available: free(managedOllamaFacadePort), - want: managedPortPlan{Enabled: true}, - }, - { - name: "running default engine is never moved", - enabled: true, - status: ollamaPortStatus{Running: true, Port: managedOllamaFacadePort}, - available: free(managedOllamaFacadePort, managedOllamaBackendStart), - want: managedPortPlan{Blocked: "Ollama is already running on the compatibility port"}, - }, - { - name: "unknown facade owner is never touched", - enabled: true, - status: ollamaPortStatus{Port: managedOllamaFacadePort}, - available: free(managedOllamaBackendStart), - want: managedPortPlan{Blocked: "the compatibility port is already in use"}, - }, - } { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.want, planManagedOllamaPorts(tc.enabled, tc.status, tc.available), "plan") + test := func(name string, enabled bool, status ollamaPortStatus, available func(int) bool, want managedPortPlan) { + t.Run(name, func(t *testing.T) { + require.Equal(t, want, planManagedOllamaPorts(enabled, status, available)) }) } + test("policy disabled changes nothing", false, ollamaPortStatus{}, + free(managedOllamaFacadePort, managedOllamaBackendStart), managedPortPlan{}) + test("stopped default engine moves behind facade", true, ollamaPortStatus{Port: managedOllamaFacadePort}, + free(managedOllamaFacadePort, managedOllamaBackendStart), managedPortPlan{Enabled: true, BackendPort: managedOllamaBackendStart}) + test("allocator skips occupied preferred backend", true, ollamaPortStatus{Port: managedOllamaFacadePort}, + free(managedOllamaFacadePort, managedOllamaBackendStart+1), managedPortPlan{Enabled: true, BackendPort: managedOllamaBackendStart + 1}) + test("occupied stopped backend advances", true, ollamaPortStatus{Port: managedOllamaBackendStart}, + free(managedOllamaFacadePort, managedOllamaBackendStart+1), managedPortPlan{Enabled: true, BackendPort: managedOllamaBackendStart + 1}) + test("running backend is preserved", true, ollamaPortStatus{Running: true, Port: managedOllamaBackendStart}, + free(managedOllamaFacadePort, managedOllamaBackendStart+1), managedPortPlan{Enabled: true}) + test("custom backend is preserved", true, ollamaPortStatus{Running: true, Port: 12000}, + free(managedOllamaFacadePort), managedPortPlan{Enabled: true}) + test("running default engine is never moved", true, ollamaPortStatus{Running: true, Port: managedOllamaFacadePort}, + free(managedOllamaFacadePort, managedOllamaBackendStart), managedPortPlan{Blocked: "Ollama is already running on the compatibility port"}) + test("unknown facade owner is never touched", true, ollamaPortStatus{Port: managedOllamaFacadePort}, + free(managedOllamaBackendStart), managedPortPlan{Blocked: "the compatibility port is already in use"}) } func TestNextAvailablePort_SkipsConfiguredEnginePort(t *testing.T) { @@ -199,38 +155,34 @@ func TestEnginePortAssignment_ValidatesRequestedPort(t *testing.T) { require.True(t, ok, "valid engine set-port request") require.Equal(t, "ollama", engine, "valid engine set-port request") require.Equal(t, 11434, port, "valid engine set-port request") - for _, tc := range []struct { - method string - params string - }{ - {"engine:status", `{"engine":"ollama","port":11434}`}, - {"engine:set-port", `{"engine":"","port":11434}`}, - {"engine:set-port", `{"engine":"ollama","port":0}`}, - {"engine:install", `{"engine":"ollama","port":11433}`}, - {"engine:restart", `{"engine":"ollama","port":11433}`}, - {"engine:set-port", `{`}, - } { - _, _, ok := enginePortAssignmentRequest(tc.method, []byte(tc.params)) - require.False(t, ok, "unexpected engine set-port match for %s %s", tc.method, tc.params) + test := func(name, method, params string) { + t.Run(name, func(t *testing.T) { + _, _, ok := enginePortAssignmentRequest(method, []byte(params)) + require.False(t, ok, "unexpected engine set-port match") + }) } + test("status is not an assignment", "engine:status", `{"engine":"ollama","port":11434}`) + test("empty engine is rejected", "engine:set-port", `{"engine":"","port":11434}`) + test("zero port is rejected", "engine:set-port", `{"engine":"ollama","port":0}`) + test("install is not an assignment", "engine:install", `{"engine":"ollama","port":11433}`) + test("restart is not an assignment", "engine:restart", `{"engine":"ollama","port":11433}`) + test("malformed parameters are rejected", "engine:set-port", `{`) } func TestLMStudioSetPort_RelaysRequestedPort(t *testing.T) { port, ok := lmstudioSetPortRequest("engine:set-port", []byte(`{"engine":"lmstudio","port":1234}`)) require.True(t, ok, "valid LM Studio request") require.Equal(t, managedLMStudioFacadePort, port, "valid LM Studio request") - for _, tc := range []struct { - method string - params string - }{ - {"engine:status", `{"engine":"lmstudio","port":1234}`}, - {"engine:set-port", `{"engine":"ollama","port":1234}`}, - {"engine:set-port", `{"engine":"lmstudio","port":0}`}, - {"engine:set-port", `{`}, - } { - _, ok := lmstudioSetPortRequest(tc.method, []byte(tc.params)) - require.False(t, ok, "unexpected LM Studio set-port match for %s %s", tc.method, tc.params) + test := func(name, method, params string) { + t.Run(name, func(t *testing.T) { + _, ok := lmstudioSetPortRequest(method, []byte(params)) + require.False(t, ok, "unexpected LM Studio set-port match") + }) } + test("status is not an assignment", "engine:status", `{"engine":"lmstudio","port":1234}`) + test("another engine is rejected", "engine:set-port", `{"engine":"ollama","port":1234}`) + test("zero port is rejected", "engine:set-port", `{"engine":"lmstudio","port":0}`) + test("malformed parameters are rejected", "engine:set-port", `{`) } func TestManagedLMStudio_RejectsFacadeBackendPort(t *testing.T) { @@ -318,17 +270,8 @@ func TestLMStudioSetPort_UpdatesBackendCache(t *testing.T) { } func TestRelay_RejectsEnginePortAssignmentToActiveOllamaHostAlias(t *testing.T) { - for _, tc := range []struct { - name string - method string - params string - }{ - {name: "set Ollama port", method: "engine:set-port", params: `{"engine":"ollama","port":11433}`}, - {name: "set LM Studio port", method: "engine:set-port", params: `{"engine":"lmstudio","port":11433}`}, - {name: "start custom engine override", method: "engine:start", params: `{"engine":"custom","port":11433}`}, - {name: "install and start custom engine override", method: "engine:install", params: `{"engine":"custom","port":11433,"start":true}`}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name, method, params string) { + t.Run(name, func(t *testing.T) { brokerConn, clientConn := net.Pipe() defer brokerConn.Close() defer clientConn.Close() @@ -353,8 +296,8 @@ func TestRelay_RejectsEnginePortAssignmentToActiveOllamaHostAlias(t *testing.T) b.relayToEngine(&Message{ JSONRPC: "2.0", ID: &id, - Method: tc.method, - Params: json.RawMessage(tc.params), + Method: method, + Params: json.RawMessage(params), }) select { @@ -369,6 +312,10 @@ func TestRelay_RejectsEnginePortAssignmentToActiveOllamaHostAlias(t *testing.T) } }) } + test("set Ollama port", "engine:set-port", `{"engine":"ollama","port":11433}`) + test("set LM Studio port", "engine:set-port", `{"engine":"lmstudio","port":11433}`) + test("start custom engine override", "engine:start", `{"engine":"custom","port":11433}`) + test("install and start custom engine override", "engine:install", `{"engine":"custom","port":11433,"start":true}`) } func TestBroker_RejectsLMStudioProxyPortAssignmentToActiveOllamaHostAlias(t *testing.T) { @@ -482,22 +429,19 @@ func TestBroker_DoesNotExposeInternalReservationSetter(t *testing.T) { } func TestNeedsOllamaPortGate_IdentifiesRequestsRequiringStablePort(t *testing.T) { - for _, tc := range []struct { - method string - params string - want bool - }{ - {"engine:get-installed", `{}`, true}, - {"engine:status", `{"engine":"ollama"}`, true}, - {"engine:install", `{"engine":"ollama"}`, true}, - {"engine:start", `{"engine":"ollama"}`, true}, - {"engine:restart", `{"engine":"ollama"}`, true}, - {"engine:status", `{"engine":"lmstudio"}`, false}, - {"engine:models", `{"engine":"ollama"}`, false}, - {"engine:status", `{`, false}, - } { - assert.Equal(t, tc.want, needsOllamaPortGate(tc.method, []byte(tc.params)), "needsOllamaPortGate(%q, %s)", tc.method, tc.params) + test := func(name, method, params string, want bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, needsOllamaPortGate(method, []byte(params))) + }) } + test("inventory waits", "engine:get-installed", `{}`, true) + test("Ollama status waits", "engine:status", `{"engine":"ollama"}`, true) + test("Ollama install waits", "engine:install", `{"engine":"ollama"}`, true) + test("Ollama start waits", "engine:start", `{"engine":"ollama"}`, true) + test("Ollama restart waits", "engine:restart", `{"engine":"ollama"}`, true) + test("LM Studio status does not wait", "engine:status", `{"engine":"lmstudio"}`, false) + test("Ollama models do not wait", "engine:models", `{"engine":"ollama"}`, false) + test("malformed parameters do not wait", "engine:status", `{`, false) } func TestOllamaPresenceRequest_WaitsForPortGate(t *testing.T) { @@ -570,24 +514,18 @@ func TestOllamaPresenceRequest_WaitsForPortGate(t *testing.T) { } func TestNextFreeProxyPort_SkipsOccupiedPorts(t *testing.T) { - for _, tc := range []struct { - name string - want int - req int - taken []int - }{ - {"free returns requested", 11435, 11435, nil}, - {"free with others taken", 11435, 11435, []int{11434, 1234}}, - {"single collision bumps by one", 11435, 11434, []int{11434}}, - {"consecutive collisions skip", 11436, 11434, []int{11434, 11435}}, - {"gap above collision", 11435, 11434, []int{11434, 11436}}, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, want, req int, takenPorts ...int) { + t.Run(name, func(t *testing.T) { taken := map[int]bool{} - for _, p := range tc.taken { + for _, p := range takenPorts { taken[p] = true } - assert.Equal(t, tc.want, nextFreeProxyPort(tc.req, taken), "nextFreeProxyPort") + assert.Equal(t, want, nextFreeProxyPort(req, taken)) }) } + test("free returns requested", 11435, 11435) + test("free with others taken", 11435, 11435, 11434, 1234) + test("single collision bumps by one", 11435, 11434, 11434) + test("consecutive collisions skip", 11436, 11434, 11434, 11435) + test("gap above collision", 11435, 11434, 11434, 11436) } diff --git a/services/nvpair-ui-broker/proxyportbump_test.go b/services/nvpair-ui-broker/proxyportbump_test.go index 0d0e7da5..1b5f4e94 100644 --- a/services/nvpair-ui-broker/proxyportbump_test.go +++ b/services/nvpair-ui-broker/proxyportbump_test.go @@ -179,47 +179,26 @@ func TestReconcileUnmanagedProxyPort_LeavesFreePortAlone(t *testing.T) { // argv carries only process-scoped flags, because a single-valued flag cannot // express a different port plan per engine. func TestOllamaFacadeSpec_PreservesPortAndAliasPolicy(t *testing.T) { - for _, tc := range []struct { - name string - startupPort int - alias ollamaHostAlias - want enableFacadeRequest - }{ - { - name: "managed facade", - startupPort: managedOllamaFacadePort, - want: enableFacadeRequest{ - Engine: "ollama", - Port: managedOllamaFacadePort, - IgnorePersistedPort: true, - }, - }, - { - // No port named, so the child keeps its persisted one. Naming a port - // here would override whatever the user last chose via set-port. - name: "no startup port leaves the proxy on its persisted or default port", - startupPort: 0, - want: enableFacadeRequest{Engine: "ollama"}, - }, - { - name: "inherited OLLAMA_HOST alias is threaded through", - startupPort: managedOllamaFacadePort, - alias: ollamaHostAlias{Address: "127.0.0.1:11433", AlternateAddress: "[::1]:11433"}, - want: enableFacadeRequest{ - Engine: "ollama", - Port: managedOllamaFacadePort, - IgnorePersistedPort: true, - AliasAddresses: []string{"127.0.0.1:11433", "[::1]:11433"}, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { + test := func(name string, startupPort int, alias ollamaHostAlias, want enableFacadeRequest) { + t.Run(name, func(t *testing.T) { b := &Broker{} - b.ollamaState().startupPort.Store(int32(tc.startupPort)) + b.ollamaState().startupPort.Store(int32(startupPort)) - require.Equal(t, tc.want, b.ollamaFacadeSpec(tc.alias), "facade spec") + require.Equal(t, want, b.ollamaFacadeSpec(alias), "facade spec") }) } + test("managed facade", managedOllamaFacadePort, ollamaHostAlias{}, enableFacadeRequest{ + Engine: "ollama", Port: managedOllamaFacadePort, IgnorePersistedPort: true, + }) + // No port named, so the child keeps its persisted one. Naming a port here + // would override whatever the user last chose via set-port. + test("no startup port preserves persisted or default port", 0, ollamaHostAlias{}, enableFacadeRequest{Engine: "ollama"}) + test("inherited OLLAMA_HOST alias is threaded through", managedOllamaFacadePort, + ollamaHostAlias{Address: "127.0.0.1:11433", AlternateAddress: "[::1]:11433"}, + enableFacadeRequest{ + Engine: "ollama", Port: managedOllamaFacadePort, IgnorePersistedPort: true, + AliasAddresses: []string{"127.0.0.1:11433", "[::1]:11433"}, + }) } // serveFacadeEnable answers facade/enable frames on a pipe, recording the port diff --git a/services/nvpair-ui-broker/settingsport_test.go b/services/nvpair-ui-broker/settingsport_test.go index a4402dd3..dd942cf0 100644 --- a/services/nvpair-ui-broker/settingsport_test.go +++ b/services/nvpair-ui-broker/settingsport_test.go @@ -41,20 +41,19 @@ func callBrokerPortRequest(t *testing.T, b *Broker, method string, params json.R func TestBrokerProxySetPort_RejectsInvalidPorts(t *testing.T) { for _, profile := range engineProxyProfiles { t.Run(profile.Name, func(t *testing.T) { - for _, tc := range []struct{ name, params string }{ - {"malformed parameters", `{`}, - {"missing port", `{}`}, - {"zero port", `{"port":0}`}, - {"negative port", `{"port":-1}`}, - {"oversized port", `{"port":65536}`}, - } { - t.Run(tc.name, func(t *testing.T) { - response := callBrokerPortRequest(t, &Broker{}, profile.ComponentName()+":set-port", json.RawMessage(tc.params)) + test := func(name, params string) { + t.Run(name, func(t *testing.T) { + response := callBrokerPortRequest(t, &Broker{}, profile.ComponentName()+":set-port", json.RawMessage(params)) require.NotNil(t, response.Error, "invalid-port error") assert.Equal(t, -32602, response.Error.Code) assert.Equal(t, "port must be between 1 and 65535", response.Error.Message) }) } + test("malformed parameters", `{`) + test("missing port", `{}`) + test("zero port", `{"port":0}`) + test("negative port", `{"port":-1}`) + test("oversized port", `{"port":65536}`) }) } } diff --git a/services/nvpair-ui-broker/supervisor_test.go b/services/nvpair-ui-broker/supervisor_test.go index f0d03011..4a1de50b 100644 --- a/services/nvpair-ui-broker/supervisor_test.go +++ b/services/nvpair-ui-broker/supervisor_test.go @@ -102,21 +102,18 @@ func waitSupervisorSignal(t *testing.T, ch <-chan struct{}, failure string) { func TestRestartPolicy_BoundsDelayAndRetryBudget(t *testing.T) { p := restartPolicy{baseDelay: time.Second, maxDelay: 16 * time.Second} - cases := []struct { - attempt int - want time.Duration - }{ - {1, 1 * time.Second}, - {2, 2 * time.Second}, - {3, 4 * time.Second}, - {4, 8 * time.Second}, - {5, 16 * time.Second}, - {6, 16 * time.Second}, // capped - {100, 16 * time.Second}, - } - for _, c := range cases { - assert.Equal(t, c.want, p.backoff(c.attempt), "backoff(%d)", c.attempt) + test := func(name string, attempt int, want time.Duration) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, p.backoff(attempt)) + }) } + test("first attempt", 1, time.Second) + test("second attempt", 2, 2*time.Second) + test("third attempt", 3, 4*time.Second) + test("fourth attempt", 4, 8*time.Second) + test("fifth attempt reaches cap", 5, 16*time.Second) + test("sixth attempt stays capped", 6, 16*time.Second) + test("hundredth attempt stays capped", 100, 16*time.Second) } // fastPolicy is a unit-test policy: near-instant backoff so restarts don't diff --git a/services/nvpair-workload-manager/workload_test.go b/services/nvpair-workload-manager/workload_test.go index 2b1d245b..67275332 100644 --- a/services/nvpair-workload-manager/workload_test.go +++ b/services/nvpair-workload-manager/workload_test.go @@ -21,17 +21,17 @@ func TestParseLifecycle_ExtractsValidWorkloadFields(t *testing.T) { } func TestParseLifecycle_RejectsMissingFields(t *testing.T) { - cases := map[string]string{ - "no workloadInfo": `{}`, - "empty id": `{"workloadInfo":{"id":"","model":"m","engine":"e","state":"queued","originatedFrom":"n"}}`, - "empty engine": `{"workloadInfo":{"id":"x","model":"m","engine":"","state":"queued","originatedFrom":"n"}}`, - "empty state": `{"workloadInfo":{"id":"x","model":"m","engine":"e","state":"","originatedFrom":"n"}}`, - "empty originatedFrom": `{"workloadInfo":{"id":"x","model":"m","engine":"e","state":"queued","originatedFrom":""}}`, - } - for name, body := range cases { - _, err := parseLifecycle(json.RawMessage(body)) - assert.Error(t, err, "%s", name) + test := func(name, body string) { + t.Run(name, func(t *testing.T) { + _, err := parseLifecycle(json.RawMessage(body)) + assert.Error(t, err) + }) } + test("no workloadInfo", `{}`) + test("empty id", `{"workloadInfo":{"id":"","model":"m","engine":"e","state":"queued","originatedFrom":"n"}}`) + test("empty engine", `{"workloadInfo":{"id":"x","model":"m","engine":"","state":"queued","originatedFrom":"n"}}`) + test("empty state", `{"workloadInfo":{"id":"x","model":"m","engine":"e","state":"","originatedFrom":"n"}}`) + test("empty originatedFrom", `{"workloadInfo":{"id":"x","model":"m","engine":"e","state":"queued","originatedFrom":""}}`) } func TestParseRemove_ExtractsWorkloadIdentity(t *testing.T) { @@ -50,15 +50,16 @@ func TestParseRemove_ExtractsWorkloadIdentity(t *testing.T) { } func TestLifecycleMethods_MapNotificationsToWorkloadStates(t *testing.T) { - for method, want := range map[string]WorkloadState{ - MethodSubmitted: StateQueued, - MethodStarted: StateRunning, - MethodCompleted: StateCompleted, - MethodErrored: StateFailed, - } { - assert.True(t, isLifecycleMethod(method), "%s should be a lifecycle method", method) - assert.Equal(t, want, lifecycleMethods[method], "%s", method) + test := func(name, method string, want WorkloadState) { + t.Run(name, func(t *testing.T) { + assert.True(t, isLifecycleMethod(method), "must be a lifecycle method") + assert.Equal(t, want, lifecycleMethods[method]) + }) } + test("submitted", MethodSubmitted, StateQueued) + test("started", MethodStarted, StateRunning) + test("completed", MethodCompleted, StateCompleted) + test("errored", MethodErrored, StateFailed) assert.False(t, isLifecycleMethod(MethodRemove), "workloads:remove is not a lifecycle method") assert.False(t, isLifecycleMethod("bogus:method"), "unknown method should not be a lifecycle method") } diff --git a/services/shared/clustertrust/mesh_test.go b/services/shared/clustertrust/mesh_test.go index 43c279ab..09f44606 100644 --- a/services/shared/clustertrust/mesh_test.go +++ b/services/shared/clustertrust/mesh_test.go @@ -39,14 +39,18 @@ func loadMeshDir(t *testing.T, certPEM, keyPEM []byte, pins map[string]string) * // but unclustered mesh, so the caller serves plain HTTP from the same object it // will later serve cluster mTLS from. func TestMeshOpen_LeavesUnclusteredNodesWithoutPeerAuthorization(t *testing.T) { - for name, dir := range map[string]string{"no cluster dir": "", "empty cluster dir": t.TempDir()} { - m := Open(dir) - require.NotNil(t, m, "%s", name) - assert.False(t, m.Clustered(), "%s", name) - assert.False(t, m.hasIdentity(), "%s", name) - m.Refresh() - assert.False(t, m.Clustered(), "%s after refresh", name) + test := func(name, dir string) { + t.Run(name, func(t *testing.T) { + m := Open(dir) + require.NotNil(t, m) + assert.False(t, m.Clustered()) + assert.False(t, m.hasIdentity()) + m.Refresh() + assert.False(t, m.Clustered(), "after refresh") + }) } + test("no cluster dir", "") + test("empty cluster dir", t.TempDir()) } // TestMesh_GateSelfTrustAndAnyPin drives the full server/client handshake the diff --git a/services/shared/engines/engines_test.go b/services/shared/engines/engines_test.go index 0275dbaf..38994adc 100644 --- a/services/shared/engines/engines_test.go +++ b/services/shared/engines/engines_test.go @@ -131,22 +131,23 @@ func TestAddressedMethod_RoundTripsEngineAndMethod(t *testing.T) { // count as an address. Treating "errors:report" as engine "errors" would strip // a real method down to "report" and route it nowhere. func TestUnaddressedMethods_AreNotMistakenForAddressed(t *testing.T) { - for _, method := range []string{ - "ready", - "errors:report", - "errors:clear", - "discovery:subscribe", - "discovery:nodes", - "discovery:node-activity", - "workload:started", - "proxy/request", - "node/selection-changed", - "log/set-level", - } { - engine, bare := SplitAddressedMethod(method) - assert.Equal(t, "", engine) - assert.Equal(t, method, bare) + test := func(method string) { + t.Run(method, func(t *testing.T) { + engine, bare := SplitAddressedMethod(method) + assert.Empty(t, engine) + assert.Equal(t, method, bare) + }) } + test("ready") + test("errors:report") + test("errors:clear") + test("discovery:subscribe") + test("discovery:nodes") + test("discovery:node-activity") + test("workload:started") + test("proxy/request") + test("node/selection-changed") + test("log/set-level") } // An engine id that is a prefix of another would make addresses ambiguous: diff --git a/services/shared/jsonrpc/jsonrpc_test.go b/services/shared/jsonrpc/jsonrpc_test.go index 75b69dd4..a77b1d52 100644 --- a/services/shared/jsonrpc/jsonrpc_test.go +++ b/services/shared/jsonrpc/jsonrpc_test.go @@ -104,11 +104,15 @@ func TestRead_RejectsBadVersionAndEOF(t *testing.T) { func TestReadMalformedFrame_IsRecoverableDecodeError(t *testing.T) { // Both bad JSON and a bad version are recoverable *DecodeError so a read // loop can continue rather than treating them as terminal. - for _, frame := range []string{"{not json}\n", `{"jsonrpc":"1.0"}` + "\n"} { - _, err := readCodec(strings.NewReader(frame)).Read() - var de *DecodeError - assert.ErrorAs(t, err, &de, "frame %q", frame) + test := func(name, frame string) { + t.Run(name, func(t *testing.T) { + _, err := readCodec(strings.NewReader(frame)).Read() + var de *DecodeError + assert.ErrorAs(t, err, &de) + }) } + test("bad JSON", "{not json}\n") + test("unsupported version", `{"jsonrpc":"1.0"}`+"\n") } func TestRespondError_RoundTripsErrorData(t *testing.T) { diff --git a/services/shared/mdns/responder_test.go b/services/shared/mdns/responder_test.go index 56545859..1fe23b1b 100644 --- a/services/shared/mdns/responder_test.go +++ b/services/shared/mdns/responder_test.go @@ -36,21 +36,15 @@ func testResponder(txt []string) *Responder { func TestNewResponder_RejectsInvalidRegistration(t *testing.T) { // These cases return before any interface enumeration, so they're // environment-independent. - cases := []struct { - name string - instance, service, domain string - port int - }{ - {"missing instance", "", "_nvpair-test._tcp", "local", 14318}, - {"missing service", "myhost", "", "local", 14318}, - {"missing port", "myhost", "_nvpair-test._tcp", "local", 0}, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - _, err := NewResponder(tc.instance, tc.service, tc.domain, tc.port, nil) - require.Error(t, err, "NewResponder") + test := func(name, instance, service, domain string, port int) { + t.Run(name, func(t *testing.T) { + _, err := NewResponder(instance, service, domain, port, nil) + require.Error(t, err) }) } + test("missing instance", "", "_nvpair-test._tcp", "local", 14318) + test("missing service", "myhost", "", "local", 14318) + test("missing port", "myhost", "_nvpair-test._tcp", "local", 0) } func TestNewResponder_NormalizesNames(t *testing.T) { diff --git a/services/shared/netpick/netpick_test.go b/services/shared/netpick/netpick_test.go index e5e03c36..bd4be032 100644 --- a/services/shared/netpick/netpick_test.go +++ b/services/shared/netpick/netpick_test.go @@ -237,13 +237,18 @@ func TestRankLocal_KeepsDirectConnectAsLastResort(t *testing.T) { // independently of each other, and confirms an unknown prefix does not disqualify // (a platform that reports an address without a mask must not lose its LAN). func TestRankLocal_DemotesNarrowPrefixesAndPointToPointInterfaces(t *testing.T) { - for _, prefixLen := range []int{30, 31, 32} { - ifaces := []localIface{ - {name: "eth1", addrs: []localAddr{{ip: "10.9.9.1", prefixLen: prefixLen}}}, - {name: "eth0", addrs: []localAddr{{ip: "10.0.0.5", prefixLen: 24}}}, - } - assert.Equal(t, "10.0.0.5", rankLocal(ifaces, Evidence{}, "")[0], "with a /%d prefix", prefixLen) + test := func(name string, prefixLen int) { + t.Run(name, func(t *testing.T) { + ifaces := []localIface{ + {name: "eth1", addrs: []localAddr{{ip: "10.9.9.1", prefixLen: prefixLen}}}, + {name: "eth0", addrs: []localAddr{{ip: "10.0.0.5", prefixLen: 24}}}, + } + assert.Equal(t, "10.0.0.5", rankLocal(ifaces, Evidence{}, "")[0]) + }) } + test("/30 prefix", 30) + test("/31 prefix", 31) + test("/32 prefix", 32) ptp := []localIface{ {name: "eth1", pointToPoint: true, addrs: []localAddr{{ip: "10.9.9.1", prefixLen: 24}}}, diff --git a/services/shared/noderec/noderec_test.go b/services/shared/noderec/noderec_test.go index d5c752ac..db3d59e3 100644 --- a/services/shared/noderec/noderec_test.go +++ b/services/shared/noderec/noderec_test.go @@ -111,26 +111,21 @@ func TestTXT_UsesDefaultSchemaWhenUnspecified(t *testing.T) { } func TestServiceTransport_RequiresMutualTLSForClusteredPeerServices(t *testing.T) { - cases := []struct { - svc ServiceKey - want Transport - mtlsClu bool // UsesMTLS when target clustered - mtlsUnclu bool // UsesMTLS when target not clustered - }{ - {ServiceNodeInfo, TransportPlain, false, false}, - {ServiceOllama, TransportPlain, false, false}, - {ServiceLMStudio, TransportPlain, false, false}, - {ServiceLlamaCPP, TransportPlain, false, false}, - {ServiceEngineManager, TransportPlain, false, false}, - {ServiceErrors, TransportMTLSWhenClustered, true, false}, - {ServiceWorkload, TransportMTLSWhenClustered, true, false}, - {ServiceCluster, TransportSplit, true, false}, - } - for _, c := range cases { - assert.Equal(t, c.want, c.svc.Transport(), "%s", c.svc) - assert.Equal(t, c.mtlsClu, c.svc.UsesMTLS(true), "%s clustered", c.svc) - assert.Equal(t, c.mtlsUnclu, c.svc.UsesMTLS(false), "%s unclustered", c.svc) + test := func(name string, svc ServiceKey, want Transport, mtlsClustered, mtlsUnclustered bool) { + t.Run(name, func(t *testing.T) { + assert.Equal(t, want, svc.Transport()) + assert.Equal(t, mtlsClustered, svc.UsesMTLS(true), "clustered") + assert.Equal(t, mtlsUnclustered, svc.UsesMTLS(false), "unclustered") + }) } + test("node info", ServiceNodeInfo, TransportPlain, false, false) + test("Ollama", ServiceOllama, TransportPlain, false, false) + test("LM Studio", ServiceLMStudio, TransportPlain, false, false) + test("llama.cpp", ServiceLlamaCPP, TransportPlain, false, false) + test("engine manager", ServiceEngineManager, TransportPlain, false, false) + test("errors", ServiceErrors, TransportMTLSWhenClustered, true, false) + test("workloads", ServiceWorkload, TransportMTLSWhenClustered, true, false) + test("cluster", ServiceCluster, TransportSplit, true, false) } func TestNodeInfoTransport_UsesPlainHTTPRegardlessOfMembership(t *testing.T) { diff --git a/services/tests/llamacpp_interop_test.go b/services/tests/llamacpp_interop_test.go index d70c8d87..612688c4 100644 --- a/services/tests/llamacpp_interop_test.go +++ b/services/tests/llamacpp_interop_test.go @@ -40,15 +40,9 @@ func TestBrokerLlamaCPPProxySetPort_RejectsInvalidPorts(t *testing.T) { } }() waitForMethod(t, msgs, "app:ready", 10*time.Second) - for i, tc := range []struct{ name, params string }{ - {"missing port", `{}`}, - {"zero port", `{"port":0}`}, - {"negative port", `{"port":-1}`}, - {"oversized port", `{"port":65536}`}, - } { - t.Run(tc.name, func(t *testing.T) { - id := 7300 + i - _, err := fmt.Fprintf(stdin, `{"jsonrpc":"2.0","id":%d,"method":"llamacpp-proxy:set-port","params":%s}`+"\n", id, tc.params) + test := func(name, params string, id int) { + t.Run(name, func(t *testing.T) { + _, err := fmt.Fprintf(stdin, `{"jsonrpc":"2.0","id":%d,"method":"llamacpp-proxy:set-port","params":%s}`+"\n", id, params) require.NoError(t, err, "write proxy port request") response := waitForResponse(t, msgs, 10*time.Second) require.NotNil(t, response.ID, "response ID") @@ -58,6 +52,10 @@ func TestBrokerLlamaCPPProxySetPort_RejectsInvalidPorts(t *testing.T) { assert.Equal(t, "port must be between 1 and 65535", response.Error.Message) }) } + test("missing port", `{}`, 7300) + test("zero port", `{"port":0}`, 7301) + test("negative port", `{"port":-1}`, 7302) + test("oversized port", `{"port":65536}`, 7303) } func TestLlamaCPPFacade_UsesRouterInventoryAndExactModelIDs(t *testing.T) { diff --git a/services/tests/model_routing_interop_test.go b/services/tests/model_routing_interop_test.go index 30f2f43b..5812bebb 100644 --- a/services/tests/model_routing_interop_test.go +++ b/services/tests/model_routing_interop_test.go @@ -62,31 +62,19 @@ func TestModelRouting_RejectsNodesWithoutRequestedModelAcrossProcesses(t *testin ollamaPort := waitProxyReady(t, stdin, msgs, 15*time.Second) lmstudioPort := waitLMStudioProxyReady(t, stdin, msgs, 15*time.Second) - type proxyCase struct { - name string - rpcPrefix string - path string - port int - } - cases := []proxyCase{ - {name: "ollama", rpcPrefix: "ollama-proxy", path: "/api/chat", port: ollamaPort}, - {name: "ollama-anthropic", rpcPrefix: "ollama-proxy", path: "/v1/messages", port: ollamaPort}, - {name: "lmstudio", rpcPrefix: "lmstudio-proxy", path: "/v1/chat/completions", port: lmstudioPort}, - {name: "lmstudio-anthropic", rpcPrefix: "lmstudio-proxy", path: "/v1/messages", port: lmstudioPort}, - } client := &http.Client{Timeout: 5 * time.Second} t.Cleanup(client.CloseIdleConnections) snapshot := func() [3]int32 { return [3]int32{owner404.hits.Load(), ownerOK.hits.Load(), ineligible.hits.Load()} } - for caseIndex, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - targetModel := "strict-routing-" + tc.name - owner404ID := tc.name + "-owner-404" - ownerOKID := tc.name + "-owner-ok" - missingID := tc.name + "-known-missing" - unknownID := tc.name + "-unknown" + test := func(name, rpcPrefix, path string, port, caseIndex int) { + t.Run(name, func(t *testing.T) { + targetModel := "strict-routing-" + name + owner404ID := name + "-owner-404" + ownerOKID := name + "-owner-ok" + missingID := name + "-known-missing" + unknownID := name + "-unknown" nodes := []struct { id string port int @@ -109,10 +97,10 @@ func TestModelRouting_RejectsNodesWithoutRequestedModelAcrossProcesses(t *testin if node.models != nil { params["models"] = node.models } - callBrokerRPC(t, stdin, msgs, requestID, tc.rpcPrefix+":node/add-manual", params) + callBrokerRPC(t, stdin, msgs, requestID, rpcPrefix+":node/add-manual", params) requestID++ } - callBrokerRPC(t, stdin, msgs, requestID, tc.rpcPrefix+":node/set-priority", map[string]any{ + callBrokerRPC(t, stdin, msgs, requestID, rpcPrefix+":node/set-priority", map[string]any{ // Both facades share one proxy process, so every snapshot must // advance the process-wide generation. "generation": caseIndex + 1, @@ -120,7 +108,7 @@ func TestModelRouting_RejectsNodesWithoutRequestedModelAcrossProcesses(t *testin }) before := snapshot() - endpoint := fmt.Sprintf("http://127.0.0.1:%d%s", tc.port, tc.path) + endpoint := fmt.Sprintf("http://127.0.0.1:%d%s", port, path) resp, err := client.Post(endpoint, "application/json", bytes.NewBufferString(fmt.Sprintf(`{"model":%q,"messages":[]}`, targetModel))) require.NoError(t, err, "target-model request failed") @@ -142,4 +130,8 @@ func TestModelRouting_RejectsNodesWithoutRequestedModelAcrossProcesses(t *testin require.Equal(t, before, after, "ownerless request reached an upstream") }) } + test("ollama", "ollama-proxy", "/api/chat", ollamaPort, 0) + test("ollama-anthropic", "ollama-proxy", "/v1/messages", ollamaPort, 1) + test("lmstudio", "lmstudio-proxy", "/v1/chat/completions", lmstudioPort, 2) + test("lmstudio-anthropic", "lmstudio-proxy", "/v1/messages", lmstudioPort, 3) } diff --git a/services/tests/ollama_host_alias_test.go b/services/tests/ollama_host_alias_test.go index 6a1dcad5..4d37dbd9 100644 --- a/services/tests/ollama_host_alias_test.go +++ b/services/tests/ollama_host_alias_test.go @@ -57,18 +57,22 @@ func TestOllamaHostAlias_ForwardsRequestsThroughBrokerManagedProxy(t *testing.T) waitForResponse(t, msgs, 5*time.Second) client := &http.Client{Timeout: 5 * time.Second} - for _, host := range []string{"127.0.0.1", "[::1]"} { - resp, err := client.Post( - fmt.Sprintf("http://%s:%d/api/chat", host, aliasPort), - "application/json", - strings.NewReader(`{"model":"alias-e2e-model","messages":[]}`), - ) - require.NoError(t, err, "POST through inherited OLLAMA_HOST alias (%v, %v)", host, err) - body, _ := io.ReadAll(resp.Body) - _ = resp.Body.Close() - require.Equal(t, http.StatusOK, resp.StatusCode, "alias (%v, %v)", host, body) - require.Contains(t, string(body), "routed-through-alias", "alias (%v, %v)", host, body) + test := func(name, host string) { + t.Run(name, func(t *testing.T) { + resp, err := client.Post( + fmt.Sprintf("http://%s:%d/api/chat", host, aliasPort), + "application/json", + strings.NewReader(`{"model":"alias-e2e-model","messages":[]}`), + ) + require.NoError(t, err, "POST through inherited OLLAMA_HOST alias (%v, %v)", host, err) + body, _ := io.ReadAll(resp.Body) + _ = resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, "alias response %s", body) + require.Contains(t, string(body), "routed-through-alias", "alias response") + }) } + test("IPv4 loopback", "127.0.0.1") + test("IPv6 loopback", "[::1]") wantStates := map[string]bool{"running": false, "completed": false} deadline := time.After(10 * time.Second) From d4f124fbba1ffe65044aa8b47143e5545f48e36c Mon Sep 17 00:00:00 2001 From: Kaylee Lubick Date: Tue, 20 Oct 2026 14:32:13 -0400 Subject: [PATCH 2/4] Check errors in Go tests and fixtures Signed-off-by: Kaylee Lubick --- .../inference-dispatcher/dispatcher_test.go | 42 ++++-- services/eap-noob/eapnoob_test.go | 15 +- services/eap-noob/example_test.go | 10 +- .../admission_migration_test.go | 3 +- .../nvpair-cluster-manager/connreuse_test.go | 2 +- .../invite_expiry_inbound_test.go | 6 +- .../members_remove_boundary_test.go | 2 +- .../nvpair-cluster-manager/peer_addr_test.go | 9 +- .../nvpair-cluster-manager/reconnect_test.go | 4 +- .../nvpair-cluster-manager/roster_test.go | 20 ++- .../self_remove_boundary_test.go | 7 +- .../self_remove_test.go | 41 ++++-- .../nvpair-engine-manager/catalog_test.go | 11 +- .../nvpair-engine-manager/connreuse_test.go | 3 +- services/nvpair-engine-manager/e2e_test.go | 19 ++- .../nvpair-engine-manager/executor_test.go | 112 +++++++------- .../health_connections_test.go | 8 +- .../install_artifacts_test.go | 8 +- .../install_linux_test.go | 2 +- .../install_windows_test.go | 2 +- services/nvpair-engine-manager/ipc_test.go | 2 +- .../launch_controls_test.go | 2 +- .../lifecycle_stop_test.go | 47 +++--- .../nvpair-engine-manager/live_pound_test.go | 30 +++- services/nvpair-engine-manager/live_test.go | 6 +- .../nvpair-engine-manager/manager_test.go | 8 +- .../nvpair-engine-manager/models_gate_test.go | 32 ++-- services/nvpair-engine-manager/models_test.go | 5 +- .../nvpair-engine-manager/modelsdir_test.go | 10 +- .../portoverride_test.go | 3 +- .../nvpair-engine-manager/progress_test.go | 2 +- .../nvpair-engine-manager/provenance_test.go | 2 +- .../nvpair-engine-manager/remediation_test.go | 49 ++++--- .../remoteclient_test.go | 6 +- .../nvpair-engine-manager/settings_test.go | 4 +- .../settingsremote_test.go | 41 ++++-- .../nvpair-engine-manager/shutdown_test.go | 3 +- services/nvpair-errors/cluster_mtls_test.go | 15 +- services/nvpair-errors/manager_test.go | 67 +++++---- services/nvpair-errors/peersync_test.go | 6 +- .../nvpair-job-scheduler/delivery_test.go | 6 +- .../nvpair-job-scheduler/schedule_test.go | 36 +++-- .../nvpair-job-scheduler/telemetry_test.go | 2 +- services/nvpair-manual-nodes/manager_test.go | 90 +++++++----- .../nvpair-node-info/cluster_mtls_test.go | 17 ++- .../nvpair-node-info/observed_wiring_test.go | 17 ++- .../cluster_identity_refresh_test.go | 8 +- .../nvpair-node-scanner/connreuse_test.go | 3 +- services/nvpair-node-scanner/daemon_test.go | 19 ++- .../nvpair-node-scanner/ghost_node_test.go | 4 +- .../node_info_outage_test.go | 10 +- services/nvpair-node-scanner/refresh_test.go | 9 +- .../nvpair-node-scanner/telemetry_test.go | 22 +-- services/nvpair-node-scanner/tls_test.go | 5 +- services/nvpair-node-settings/manager_test.go | 50 ++++--- services/nvpair-proxy/activity_test.go | 7 +- services/nvpair-proxy/alias_test.go | 30 ++-- .../nvpair-proxy/choose_reachable_test.go | 28 ++-- services/nvpair-proxy/cors_generic_test.go | 18 +-- services/nvpair-proxy/cors_test.go | 13 +- services/nvpair-proxy/e2e_test.go | 27 ++-- services/nvpair-proxy/failover_test.go | 48 ++++-- services/nvpair-proxy/firstbody_test.go | 9 +- services/nvpair-proxy/portstore_test.go | 6 +- services/nvpair-proxy/retry_test.go | 12 +- services/nvpair-proxy/twofacade_test.go | 4 +- services/nvpair-proxy/workload_test.go | 4 +- services/nvpair-proxy/zombie_test.go | 16 +- services/nvpair-tui/rpc/client_test.go | 14 +- services/nvpair-tui/supervisor_test.go | 46 +++++- services/nvpair-tui/ui/catalog_test.go | 4 +- services/nvpair-tui/ui/demo_test.go | 8 +- services/nvpair-tui/ui/engineswire_test.go | 39 +++-- services/nvpair-tui/ui/logoutput_test.go | 12 +- services/nvpair-tui/ui/model_test.go | 5 +- services/nvpair-tui/ui/nodedetail_test.go | 42 +++--- services/nvpair-tui/ui/nodenames_test.go | 8 +- services/nvpair-tui/ui/nodetelemetry_test.go | 19 ++- services/nvpair-tui/ui/service_test.go | 2 +- services/nvpair-tui/ui/toast_test.go | 103 ++++++++----- services/nvpair-tui/ui/updatecheck_test.go | 12 +- services/nvpair-ui-broker/advertiser_test.go | 16 +- .../nvpair-ui-broker/broker_lifecycle_test.go | 8 +- services/nvpair-ui-broker/brokertest_test.go | 24 ++- .../nvpair-ui-broker/enginefacade_test.go | 14 +- services/nvpair-ui-broker/engineproxy_test.go | 8 +- .../enginesettings_security_test.go | 3 +- .../nvpair-ui-broker/enginesettings_test.go | 37 ++--- .../health_connections_test.go | 5 +- .../nvpair-ui-broker/lmstudioport_test.go | 137 +++++++++--------- .../nvpair-ui-broker/nodeactivity_test.go | 4 +- .../nodeinfo_observed_test.go | 5 +- services/nvpair-ui-broker/ollamahost_test.go | 36 ++--- .../nvpair-ui-broker/proxyaddressing_test.go | 4 +- services/nvpair-ui-broker/proxyport_test.go | 18 ++- .../nvpair-ui-broker/proxyportbump_test.go | 28 ++-- services/nvpair-ui-broker/scheduler_test.go | 9 +- .../nvpair-ui-broker/settingsport_test.go | 7 +- .../nvpair-ui-broker/staleworkload_test.go | 12 +- .../nvpair-ui-broker/workloadmanager_test.go | 22 +-- .../workloadstore/persistence_test.go | 40 ++--- .../workloadstore/staleness_test.go | 56 +++---- .../workloadstore/store_test.go | 128 ++++++++-------- .../cluster_mtls_test.go | 33 +++-- services/nvpair-workload-manager/push_test.go | 37 +++-- .../relaysource_test.go | 6 +- .../shared/clustertrust/clustertrust_test.go | 21 ++- services/shared/clustertrust/mesh_test.go | 9 +- .../shared/clustertrust/peerclient_test.go | 14 +- services/shared/clustertrust/reload_test.go | 2 +- services/shared/cors/cors_test.go | 16 +- .../shared/httpcon/testclient/client_test.go | 3 +- services/shared/jsonrpc/jsonrpc_test.go | 10 +- services/shared/jsonrpc/peer_test.go | 26 +++- services/shared/noderec/noderec_test.go | 2 +- .../shared/splitlisten/splitlisten_test.go | 28 ++-- services/tests/broker_management_test.go | 14 +- services/tests/broker_supervision_test.go | 9 +- services/tests/broker_test.go | 23 +-- services/tests/cluster_data_plane_test.go | 21 ++- services/tests/cluster_manager_test.go | 22 +-- services/tests/cluster_restore_test.go | 7 +- services/tests/discovery_test.go | 2 +- services/tests/errors_test.go | 22 +-- services/tests/ghost_node_test.go | 17 ++- services/tests/llamacpp_interop_test.go | 63 ++++---- services/tests/log_level_test.go | 4 +- services/tests/main_test.go | 12 +- services/tests/model_routing_interop_test.go | 16 +- services/tests/models_http_test.go | 18 ++- services/tests/models_refresh_test.go | 17 ++- services/tests/node_settings_test.go | 15 +- services/tests/ollama_host_alias_test.go | 12 +- services/tests/remote_engine_test.go | 8 +- services/tests/scheduler_interop_test.go | 22 +-- services/tests/secure_inference_test.go | 33 +++-- services/tests/tls_test.go | 3 +- .../tests/workload_identity_interop_test.go | 37 +++-- services/tests/workload_interop_test.go | 33 +++-- 139 files changed, 1647 insertions(+), 1111 deletions(-) diff --git a/scripts/inference-dispatcher/dispatcher_test.go b/scripts/inference-dispatcher/dispatcher_test.go index b10efd15..7dab4f3e 100644 --- a/scripts/inference-dispatcher/dispatcher_test.go +++ b/scripts/inference-dispatcher/dispatcher_test.go @@ -56,11 +56,12 @@ func TestOmittedModel_SelectsAvailableGenerationModel(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/tags": - _, _ = w.Write([]byte(`{"models":[ + _, err := w.Write([]byte(`{"models":[ {"name":"z-embed","capabilities":["embedding"]}, {"name":"b-chat","capabilities":["chat"]}, {"name":"a-completion","capabilities":["completion"]} ]}`)) + assert.NoError(t, err, "write engine response") case "/api/generate": var request struct { Model string `json:"model"` @@ -69,7 +70,8 @@ func TestOmittedModel_SelectsAvailableGenerationModel(t *testing.T) { return } receivedModel = request.Model - _, _ = w.Write([]byte(`{"response":"hello"}`)) + _, err := w.Write([]byte(`{"response":"hello"}`)) + assert.NoError(t, err, "write engine response") default: http.NotFound(w, r) } @@ -96,7 +98,8 @@ func TestExplicitModel_SkipsInventoryRequest(t *testing.T) { assert.Fail(t, "explicit model unexpectedly queried inventory") return } - _, _ = w.Write([]byte(`{"response":"ok"}`)) + _, err := w.Write([]byte(`{"response":"ok"}`)) + assert.NoError(t, err, "write engine response") })) defer server.Close() @@ -118,9 +121,11 @@ func TestLMStudio_FallsBackToOpenAIInventory(t *testing.T) { case "/api/v1/models": http.Error(w, "not found", http.StatusNotFound) case "/v1/models": - _, _ = w.Write([]byte(`{"data":[{"id":"live-model"}]}`)) + _, err := w.Write([]byte(`{"data":[{"id":"live-model"}]}`)) + assert.NoError(t, err, "write engine response") case "/v1/chat/completions": - _, _ = w.Write([]byte(`{"choices":[{"message":{"content":"done"}}]}`)) + _, err := w.Write([]byte(`{"choices":[{"message":{"content":"done"}}]}`)) + assert.NoError(t, err, "write engine response") default: http.NotFound(w, r) } @@ -152,7 +157,8 @@ func TestLlamaCPP_UsesOpenAIInventoryAndChat(t *testing.T) { switch { case r.Method == http.MethodGet && r.URL.Path == "/v1/models": observed <- observedRequest{method: r.Method, path: r.URL.Path} - _, _ = w.Write([]byte(`{"data":[{"id":"llama-demo"}]}`)) + _, err := w.Write([]byte(`{"data":[{"id":"llama-demo"}]}`)) + assert.NoError(t, err) case r.Method == http.MethodPost && r.URL.Path == "/v1/chat/completions": var request struct { Model string `json:"model"` @@ -168,7 +174,8 @@ func TestLlamaCPP_UsesOpenAIInventoryAndChat(t *testing.T) { model: request.Model, messageCount: len(request.Messages), } - _, _ = w.Write([]byte(`{"choices":[{"message":{"content":"done"}}]}`)) + _, err := w.Write([]byte(`{"choices":[{"message":{"content":"done"}}]}`)) + assert.NoError(t, err) default: http.NotFound(w, r) } @@ -199,16 +206,18 @@ func TestLMStudio_PrefersAggregatedInventory(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/models": - _, _ = w.Write([]byte(`{"data":[ + _, err := w.Write([]byte(`{"data":[ {"id":"remote-chat"}, {"id":"local-embed"}, {"id":"local-chat"} ]}`)) + assert.NoError(t, err, "write engine response") case "/api/v1/models": - _, _ = w.Write([]byte(`{"models":[ + _, err := w.Write([]byte(`{"models":[ {"key":"local-embed","type":"embeddings"}, {"key":"local-chat","type":"llm"} ]}`)) + assert.NoError(t, err, "write engine response") default: http.NotFound(w, r) } @@ -242,7 +251,8 @@ func TestLMStudio_PrefersAggregatedInventory(t *testing.T) { func TestListModels_EmitsJSON(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write([]byte(`{"models":[{"name":"test-model"}]}`)) + _, err := w.Write([]byte(`{"models":[{"name":"test-model"}]}`)) + assert.NoError(t, err, "write engine response") })) defer server.Close() @@ -281,7 +291,8 @@ func TestCancellation_StopsInFlightRequestCleanly(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/tags": - _, _ = w.Write([]byte(`{"models":[{"name":"available"}]}`)) + _, err := w.Write([]byte(`{"models":[{"name":"available"}]}`)) + assert.NoError(t, err, "write engine response") case "/api/generate": once.Do(func() { close(requestStarted) }) <-releaseServer @@ -350,9 +361,11 @@ func TestResponseText_NeverReachesStdout(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/tags": - _, _ = w.Write([]byte(`{"models":[{"name":"available"}]}`)) + _, err := w.Write([]byte(`{"models":[{"name":"available"}]}`)) + assert.NoError(t, err, "write engine response") case "/api/generate": - _, _ = w.Write([]byte(`{"response":"` + secret + `"}`)) + _, err := w.Write([]byte(`{"response":"` + secret + `"}`)) + assert.NoError(t, err, "write engine response") default: http.NotFound(w, r) } @@ -376,7 +389,8 @@ func TestUpstreamErrorBody_NeverReachesLogs(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/tags": - _, _ = w.Write([]byte(`{"models":[{"name":"available"}]}`)) + _, err := w.Write([]byte(`{"models":[{"name":"available"}]}`)) + assert.NoError(t, err, "write engine response") default: http.Error(w, `{"error":{"message":"`+echoed+`"}}`, http.StatusBadRequest) } diff --git a/services/eap-noob/eapnoob_test.go b/services/eap-noob/eapnoob_test.go index 9ca23cd6..a323f937 100644 --- a/services/eap-noob/eapnoob_test.go +++ b/services/eap-noob/eapnoob_test.go @@ -98,11 +98,15 @@ func TestPairing_DerivesMatchingSecretsAcrossSuitesAndDirections(t *testing.T) { assert.Equal(t, pSecret, sSecret, "exported secret mismatch at length %d", n) } - a, _ := srv.Export("label-a", nil, 32) - b, _ := srv.Export("label-b", nil, 32) + a, err := srv.Export("label-a", nil, 32) + require.NoError(t, err, "export label-a") + b, err := srv.Export("label-b", nil, 32) + require.NoError(t, err, "export label-b") assert.NotEqual(t, b, a, "different labels produced identical secrets") - c1, _ := srv.Export("app", []byte("ctx1"), 32) - c2, _ := srv.Export("app", []byte("ctx2"), 32) + c1, err := srv.Export("app", []byte("ctx1"), 32) + require.NoError(t, err, "export ctx1") + c2, err := srv.Export("app", []byte("ctx2"), 32) + require.NoError(t, err, "export ctx2") assert.NotEqual(t, c2, c1, "different contexts produced identical secrets") }) } @@ -126,7 +130,8 @@ func TestPairing_RejectsTamperedMAC(t *testing.T) { // Run the Completion handshake manually and corrupt MACp before the server // verifies it. - req1, _ := srv.Start() + req1, err := srv.Start() + require.NoError(t, err, "start completion discovery") resp1, err := peer.Receive(req1) // Type=1 response require.NoError(t, err, "peer discovery:") req6, err := srv.Receive(resp1.Send) // Type=6 request (MACs) diff --git a/services/eap-noob/example_test.go b/services/eap-noob/example_test.go index 860aeca9..0e51c2a4 100644 --- a/services/eap-noob/example_test.go +++ b/services/eap-noob/example_test.go @@ -67,8 +67,14 @@ func Example() { // Both sides now share an association and can derive a secret of any length. const secretLen = 48 - serverSecret, _ := srv.Export("my-application", nil, secretLen) - peerSecret, _ := peer.Export("my-application", nil, secretLen) + serverSecret, err := srv.Export("my-application", nil, secretLen) + if err != nil { + panic(err) + } + peerSecret, err := peer.Export("my-application", nil, secretLen) + if err != nil { + panic(err) + } fmt.Println("registered:", srv.State() == eapnoob.StateRegistered) fmt.Println("secret length:", len(serverSecret)) diff --git a/services/nvpair-cluster-manager/admission_migration_test.go b/services/nvpair-cluster-manager/admission_migration_test.go index 80001a5a..9da881f3 100644 --- a/services/nvpair-cluster-manager/admission_migration_test.go +++ b/services/nvpair-cluster-manager/admission_migration_test.go @@ -27,7 +27,8 @@ func TestLegacyPairingInfo_MapsToFirstAdmission(t *testing.T) { info.AdmissionEpoch = 0 info.V = pairingInfoVersion - raw, _ = json.Marshal(info) + raw, err = json.Marshal(info) + require.NoError(t, err) _, _, err = parsePairingInfo(raw) require.Error(t, err, "v2 pairing info without admission epoch was accepted") } diff --git a/services/nvpair-cluster-manager/connreuse_test.go b/services/nvpair-cluster-manager/connreuse_test.go index 550c5aed..c22dcd0d 100644 --- a/services/nvpair-cluster-manager/connreuse_test.go +++ b/services/nvpair-cluster-manager/connreuse_test.go @@ -42,7 +42,7 @@ func TestReconcile_ReusesPeerConnections(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc(rosterPath, func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"clusterId":"cluster-1"}`)) + writeTestResponse(t, w, []byte(`{"clusterId":"cluster-1"}`)) }) ts := httptest.NewUnstartedServer(mux) ts.Config.ConnState = func(_ net.Conn, state http.ConnState) { diff --git a/services/nvpair-cluster-manager/invite_expiry_inbound_test.go b/services/nvpair-cluster-manager/invite_expiry_inbound_test.go index 41521b19..7b184b08 100644 --- a/services/nvpair-cluster-manager/invite_expiry_inbound_test.go +++ b/services/nvpair-cluster-manager/invite_expiry_inbound_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -90,8 +91,9 @@ func TestExpireInboundInvite_SignalsInviter(t *testing.T) { return } var env pairingEnvelope - body, _ := io.ReadAll(io.LimitReader(r.Body, maxBodyBytes)) - _ = json.Unmarshal(body, &env) + body, err := io.ReadAll(io.LimitReader(r.Body, maxBodyBytes)) + assert.NoError(t, err) + assert.NoError(t, json.Unmarshal(body, &env)) respondPairing(w, nil) select { case got <- env: diff --git a/services/nvpair-cluster-manager/members_remove_boundary_test.go b/services/nvpair-cluster-manager/members_remove_boundary_test.go index afd9f48e..b5d6428f 100644 --- a/services/nvpair-cluster-manager/members_remove_boundary_test.go +++ b/services/nvpair-cluster-manager/members_remove_boundary_test.go @@ -79,7 +79,7 @@ func TestMembersRemove_RevalidatesAfterBlockedBody(t *testing.T) { victim.addSelfMember() _, err = pw.Write(payload) require.NoError(t, err) - _ = pw.Close() + require.NoError(t, pw.Close()) select { case <-done: diff --git a/services/nvpair-cluster-manager/peer_addr_test.go b/services/nvpair-cluster-manager/peer_addr_test.go index b52ac7ac..f379cadb 100644 --- a/services/nvpair-cluster-manager/peer_addr_test.go +++ b/services/nvpair-cluster-manager/peer_addr_test.go @@ -34,8 +34,8 @@ func TestReconcileWith_FailsOverToAnAnsweringAddress(t *testing.T) { mA := newTestManagerPort(t, 15031) mB := newTestManagerPort(t, 15032) - go func() { _ = mA.runHTTP(ctx) }() - go func() { _ = mB.runHTTP(ctx) }() + startTestHTTP(t, mA, ctx) + startTestHTTP(t, mB, ctx) time.Sleep(400 * time.Millisecond) pinTrusted(t, mA, mB.identity.NodeUUID, string(mB.identity.CertPEM), mB.identity.CertFingerprint) @@ -148,7 +148,8 @@ func TestReachableEndpointFirst_LeadsWithTheAddressThatAnswers(t *testing.T) { _ = c.Close() } }() - _, portStr, _ := net.SplitHostPort(ln.Addr().String()) + _, portStr, err := net.SplitHostPort(ln.Addr().String()) + require.NoError(t, err) answering := net.JoinHostPort("127.0.0.1", portStr) require.Equal(t, []string{answering, unreachableAddr}, reachableEndpointFirst([]string{unreachableAddr, answering}), "reachableEndpointFirst") @@ -174,7 +175,7 @@ func pairingProbeServer(t *testing.T, status int, body string) (addr string, req requests.Add(1) w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - _, _ = w.Write([]byte(body)) + writeTestResponse(t, w, []byte(body)) }) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) diff --git a/services/nvpair-cluster-manager/reconnect_test.go b/services/nvpair-cluster-manager/reconnect_test.go index 2fb3daf4..f1d3768d 100644 --- a/services/nvpair-cluster-manager/reconnect_test.go +++ b/services/nvpair-cluster-manager/reconnect_test.go @@ -44,8 +44,8 @@ func TestHandleRoster_LearnsSourceAddr(t *testing.T) { mA := newTestManagerPort(t, 15021) mB := newTestManagerPort(t, 15022) - go func() { _ = mA.runHTTP(ctx) }() - go func() { _ = mB.runHTTP(ctx) }() + startTestHTTP(t, mA, ctx) + startTestHTTP(t, mB, ctx) time.Sleep(400 * time.Millisecond) pinTrusted(t, mA, mB.identity.NodeUUID, string(mB.identity.CertPEM), mB.identity.CertFingerprint) diff --git a/services/nvpair-cluster-manager/roster_test.go b/services/nvpair-cluster-manager/roster_test.go index 4bafdb3d..7d73017e 100644 --- a/services/nvpair-cluster-manager/roster_test.go +++ b/services/nvpair-cluster-manager/roster_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,8 +28,8 @@ func TestReconcile_ContactsPeersOverMutualTLS(t *testing.T) { mA := newTestManagerPort(t, 15011) mB := newTestManagerPort(t, 15012) - go func() { _ = mA.runHTTP(ctx) }() - go func() { _ = mB.runHTTP(ctx) }() + startTestHTTP(t, mA, ctx) + startTestHTTP(t, mB, ctx) time.Sleep(400 * time.Millisecond) // A and B trust each other (simulated completed pairing), with mutual member @@ -75,6 +76,17 @@ func newTestManagerPort(t *testing.T, port int) *Manager { return mgr } +func startTestHTTP(t *testing.T, m *Manager, parent context.Context) { + t.Helper() + ctx, cancel := context.WithCancel(parent) + done := make(chan error, 1) + go func() { done <- m.runHTTP(ctx) }() + t.Cleanup(func() { + cancel() + assert.NoError(t, <-done) + }) +} + // makeNode mints a fresh node identity (uuid + self-signed leaf) and returns its // signing key, simulating a real peer for endorsement crafting. func makeNode(t *testing.T, host string) (uuid, certPEM, fingerprint string, priv ed25519.PrivateKey) { @@ -84,9 +96,11 @@ func makeNode(t *testing.T, host string) (uuid, certPEM, fingerprint string, pri certPEMb, keyPEMb, err := generateLeaf(uuid, host) require.NoError(t, err, "leaf") block, _ := pem.Decode(keyPEMb) + require.NotNil(t, block) k, err := x509.ParsePKCS8PrivateKey(block.Bytes) require.NoError(t, err, "parse key") - fp, _ := certFingerprintFromPEM(certPEMb) + fp, err := certFingerprintFromPEM(certPEMb) + require.NoError(t, err) return uuid, string(certPEMb), fp, k.(ed25519.PrivateKey) } diff --git a/services/nvpair-cluster-manager/self_remove_boundary_test.go b/services/nvpair-cluster-manager/self_remove_boundary_test.go index dce6e581..0c1ea2f3 100644 --- a/services/nvpair-cluster-manager/self_remove_boundary_test.go +++ b/services/nvpair-cluster-manager/self_remove_boundary_test.go @@ -95,7 +95,8 @@ func TestSelfRemoveTeardown_SerializesRejoin(t *testing.T) { test("stale set-identity restore behind teardown is refused", func(t *testing.T, m *Manager, _ pinFixture) { // The broker reflecting its still-persisted clusterId (same id) // after the node has already been unclustered by the teardown. - params, _ := json.Marshal(map[string]string{"clusterId": "cluster-1", "clusterFriendlyName": "Restored"}) + params, err := json.Marshal(map[string]string{"clusterId": "cluster-1", "clusterFriendlyName": "Restored"}) + require.NoError(t, err) m.handleSetIdentity(&Message{Params: params}) }, func(t *testing.T, m *Manager, p1, _ pinFixture) { id, _ := m.clusterIdentity() @@ -107,10 +108,10 @@ func TestSelfRemoveTeardown_SerializesRejoin(t *testing.T) { test("full pairing commit stays atomic across teardown", func(t *testing.T, m *Manager, p2 pinFixture) { m.withClusterComposition(func() { m.setClusterIdentity("cluster-2", "Rejoined") - _ = m.trust.Pin(&TrustedPin{ + require.NoError(t, m.trust.Pin(&TrustedPin{ NodeUUID: p2.uuid, NodeID: "peer-2", ClusterID: "cluster-2", CertPem: p2.cert, CertFingerprint: p2.fp, PinnedAt: time.Now().UnixMilli(), - }) + })) m.upsertMember(&ClusterNode{NodeUUID: p2.uuid, ID: "peer-2", ClusterID: "cluster-2", State: stateMember}) m.addSelfMember() }) diff --git a/services/nvpair-cluster-manager/self_remove_test.go b/services/nvpair-cluster-manager/self_remove_test.go index 2dd30269..2719a737 100644 --- a/services/nvpair-cluster-manager/self_remove_test.go +++ b/services/nvpair-cluster-manager/self_remove_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,20 +22,38 @@ import ( // cluster, signed by the peer (whose cert the victim still pins). This mirrors // what handleRoster (rejectRosterReconcile) attaches on the not-pinned path, so // the victim's rejectionProvesRemoval accepts it as authenticated proof. -func selfRemovalProof(peer, victim *Manager, cluster string) []byte { +func selfRemovalProof(t *testing.T, peer, victim *Manager, cluster string) []byte { + t.Helper() _, victimEpoch := victim.currentAdmission() proof, err := peer.newRemovalProof(victim.identity.NodeUUID, victimEpoch) if err != nil { panic(err) } proof = peer.withLocalRelayEndorsement(proof) - b, _ := json.Marshal(rosterRejection{ + b, err := json.Marshal(rosterRejection{ Tombstones: []Tombstone{proof.Tombstone}, RemovalProofs: []RemovalProof{proof}, }) + require.NoError(t, err) return b } +func writeTestResponse(t *testing.T, w http.ResponseWriter, body []byte) { + t.Helper() + _, err := w.Write(body) + assert.NoError(t, err) +} + +func serveTestHTTP(t *testing.T, srv *http.Server, ln net.Listener) { + t.Helper() + done := make(chan error, 1) + go func() { done <- srv.Serve(ln) }() + t.Cleanup(func() { + assert.NoError(t, srv.Close()) + assert.ErrorIs(t, <-done, http.ErrServerClosed) + }) +} + // TestSelfRemoveGuardStaleVerdict is the regression for the TOCTOU flagged in // review of the offline-removed self-remove backstop. reconcilePeersAndMaybeSelfRemove // snapshots its peers, waits on network reconciles (up to pairingHTTPTimeout @@ -68,21 +87,19 @@ func TestSelfRemoval_IgnoresRejectionFromPreviousMembership(t *testing.T) { // guard rather than the "no proof, stay put" path. In the rejoin case // mA's current cluster no longer matches the tombstone, so the proof no // longer applies — which the guard also (correctly) treats as staying. - proofBody := selfRemovalProof(peer, mA, "cluster-1") + proofBody := selfRemovalProof(t, peer, mA, "cluster-1") mux := http.NewServeMux() mux.HandleFunc(rosterPath, func(w http.ResponseWriter, r *http.Request) { once.Do(func() { close(entered) }) <-release w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusForbidden) - _, _ = w.Write(proofBody) + writeTestResponse(t, w, proofBody) }) ln, err := tls.Listen("tcp", "127.0.0.1:0", peer.buildServerTLSConfig()) require.NoError(t, err, "listen") - defer ln.Close() srv := &http.Server{Handler: mux} - go func() { _ = srv.Serve(ln) }() - defer srv.Close() + serveTestHTTP(t, srv, ln) mA.upsertMember(&ClusterNode{ NodeUUID: peer.identity.NodeUUID, @@ -148,25 +165,23 @@ func startPeerStub(t *testing.T, m *Manager, id string, status int, proof bool) var proofBody []byte if proof && status == http.StatusForbidden { cid, _ := m.clusterIdentity() - proofBody = selfRemovalProof(peer, m, cid) + proofBody = selfRemovalProof(t, peer, m, cid) } mux := http.NewServeMux() mux.HandleFunc(rosterPath, func(w http.ResponseWriter, r *http.Request) { if status == http.StatusOK { w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{}`)) + writeTestResponse(t, w, []byte(`{}`)) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - _, _ = w.Write(proofBody) // nil body ⇒ a bare 403 with no removal proof + writeTestResponse(t, w, proofBody) // nil body ⇒ a bare 403 with no removal proof }) ln, err := tls.Listen("tcp", "127.0.0.1:0", peer.buildServerTLSConfig()) require.NoError(t, err, "listen") - t.Cleanup(func() { _ = ln.Close() }) srv := &http.Server{Handler: mux} - go func() { _ = srv.Serve(ln) }() - t.Cleanup(func() { _ = srv.Close() }) + serveTestHTTP(t, srv, ln) m.upsertMember(&ClusterNode{ NodeUUID: peer.identity.NodeUUID, diff --git a/services/nvpair-engine-manager/catalog_test.go b/services/nvpair-engine-manager/catalog_test.go index 73d14d65..98127021 100644 --- a/services/nvpair-engine-manager/catalog_test.go +++ b/services/nvpair-engine-manager/catalog_test.go @@ -96,7 +96,7 @@ func TestLmStudioCatalog_CoalescesConcurrentCallers(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { requests.Add(1) <-release // hold the request open so the callers genuinely overlap - _, _ = w.Write([]byte(`[{"id":"lmstudio-community/Model-GGUF","downloads":5}]`)) + writeTestResponse(t, w, []byte(`[{"id":"lmstudio-community/Model-GGUF","downloads":5}]`)) })) defer srv.Close() @@ -123,7 +123,7 @@ func TestLmStudioCatalog_CoalescesConcurrentCallers(t *testing.T) { assert.Equal(t, int32(1), requests.Load(), "upstream requests from %d callers", callers) for i := range callers { - assert.NoError(t, errs[i], "caller %d", i) + require.NoError(t, errs[i], "caller %d", i) assert.Equal(t, 1, counts[i], "caller %d model count", i) } } @@ -140,7 +140,7 @@ func TestLmStudioCatalog_BacksOffAfterFailure(t *testing.T) { w.WriteHeader(http.StatusBadGateway) return } - _, _ = w.Write([]byte(`[{"id":"lmstudio-community/Model-GGUF","downloads":5}]`)) + writeTestResponse(t, w, []byte(`[{"id":"lmstudio-community/Model-GGUF","downloads":5}]`)) })) defer srv.Close() @@ -193,7 +193,8 @@ func TestLmStudioCatalog_BacksOffWithNothingCached(t *testing.T) { c.lmStudio.mu.Lock() c.lmStudio.failed = time.Now().Add(-2 * catalogRetryAfterFailure) c.lmStudio.mu.Unlock() - _, _, _ = c.lmStudio.get(context.Background()) + _, _, err := c.lmStudio.get(context.Background()) + assert.Error(t, err, "retry against a failing upstream must still report failure") assert.Equal(t, int32(2), requests.Load(), "backoff expiry must permit a second attempt") } @@ -441,7 +442,7 @@ func llamaCPPServer(t *testing.T, failAuthor string) (*httptest.Server, func() [ case search != "": rows = []hfModelRow{ggufRow("found/"+strings.ReplaceAll(search, " ", "-"), 1)} } - _ = json.NewEncoder(w).Encode(rows) + assert.NoError(t, json.NewEncoder(w).Encode(rows), "encode catalog response") })) t.Cleanup(srv.Close) return srv, func() []string { diff --git a/services/nvpair-engine-manager/connreuse_test.go b/services/nvpair-engine-manager/connreuse_test.go index d625a37a..ab6ad6a6 100644 --- a/services/nvpair-engine-manager/connreuse_test.go +++ b/services/nvpair-engine-manager/connreuse_test.go @@ -16,6 +16,7 @@ import ( "sync" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/clustertrust" @@ -48,7 +49,7 @@ func TestRemoteClient_ReusesPeerConnections(t *testing.T) { http.Error(w, "forbidden", http.StatusForbidden) return } - _ = json.NewEncoder(w).Encode(map[string]any{"engines": []any{}}) + assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"engines": []any{}})) }) ts := httptest.NewUnstartedServer(mux) ts.Config.ConnState = func(_ net.Conn, state http.ConnState) { diff --git a/services/nvpair-engine-manager/e2e_test.go b/services/nvpair-engine-manager/e2e_test.go index f29e4f0f..3ecbf36a 100644 --- a/services/nvpair-engine-manager/e2e_test.go +++ b/services/nvpair-engine-manager/e2e_test.go @@ -8,6 +8,7 @@ import ( "encoding/json" "io" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" settings "nvpair-shared/enginesettings" @@ -50,7 +51,7 @@ func TestEngineManager_HandlesLifecycleRequestsOverStdio(t *testing.T) { }() frames := make(chan frame, 128) - go readFrames(stdout, frames) + go readFrames(t, stdout, frames) waitNotify(t, frames, "engine:ready", 5*time.Second) @@ -227,7 +228,7 @@ func startE2EManager(t *testing.T, cfg, home string, args ...string) *e2eManager _ = manager.cmd.Wait() } }) - go readFrames(stdout, manager.frames) + go readFrames(t, stdout, manager.frames) waitNotify(t, manager.frames, "engine:ready", 5*time.Second) return manager } @@ -292,12 +293,12 @@ type frame struct { Error json.RawMessage `json:"error"` } -func readFrames(r io.Reader, out chan<- frame) { +func readFrames(t *testing.T, r io.Reader, out chan<- frame) { sc := bufio.NewScanner(r) sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) for sc.Scan() { var f frame - if err := json.Unmarshal(sc.Bytes(), &f); err != nil { + if !assert.NoError(t, json.Unmarshal(sc.Bytes(), &f), "decode frame") { continue } out <- f @@ -310,8 +311,9 @@ func send(t *testing.T, w io.Writer, id int, method string, params any) { if params != nil { msg["params"] = params } - data, _ := json.Marshal(msg) - _, err := w.Write(append(data, '\n')) + data, err := json.Marshal(msg) + require.NoError(t, err) + _, err = w.Write(append(data, '\n')) require.NoError(t, err, "send (%v, %v)", method, err) } @@ -321,8 +323,9 @@ func notify(t *testing.T, w io.Writer, method string, params any) { if params != nil { msg["params"] = params } - data, _ := json.Marshal(msg) - _, err := w.Write(append(data, '\n')) + data, err := json.Marshal(msg) + require.NoError(t, err) + _, err = w.Write(append(data, '\n')) require.NoError(t, err, "notify (%v, %v)", method, err) } diff --git a/services/nvpair-engine-manager/executor_test.go b/services/nvpair-engine-manager/executor_test.go index 4031f37f..669c256e 100644 --- a/services/nvpair-engine-manager/executor_test.go +++ b/services/nvpair-engine-manager/executor_test.go @@ -80,11 +80,10 @@ func TestOnlyOllamaRunModel_UsesSlowResponseHeaderBudget(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { time.Sleep(100 * time.Millisecond) w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"done":true}`)) + writeTestResponse(t, w, []byte(`{"done":true}`)) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) m := testEngineManifest(fakeEngineBin) m.Engine = "ollama" @@ -147,7 +146,7 @@ func TestHTTPAction_SendsStringParamsInQuery(t *testing.T) { readErr: err, } w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"success":true}`)) + writeTestResponse(t, w, []byte(`{"success":true}`)) })) defer srv.Close() @@ -214,7 +213,8 @@ func TestEngineLifecycle_DetectsStartsReportsHealthAndStops(t *testing.T) { require.True(t, installed, "expected fake engine to be detected (bin (%v)", fakeEngineBin) require.NoError(t, ex.Start(ctx, "fake"), "start") - st, _ := ex.Status("fake") + st, err := ex.Status("fake") + require.NoError(t, err) require.True(t, st.Running, "expected running+healthy (%v)", st) require.True(t, st.Healthy, "expected running+healthy (%v)", st) require.NotEqual(t, 0, st.Port, "expected a non-zero port (%v)", st) @@ -224,7 +224,8 @@ func TestEngineLifecycle_DetectsStartsReportsHealthAndStops(t *testing.T) { require.Contains(t, string(res), "llama3.2", "unexpected action result (%v)", res) require.NoError(t, ex.Stop("fake"), "stop") - st, _ = ex.Status("fake") + st, err = ex.Status("fake") + require.NoError(t, err) require.False(t, st.Running, "expected stopped (%v)", st) } @@ -243,7 +244,9 @@ func TestCrashThenExternalService_IsAdoptedWithoutRespawn(t *testing.T) { if resp != nil { _ = resp.Body.Close() } - state, _ := ex.state(m.Engine) + state, err := ex.state(m.Engine) + require.NoError(t, err) + require.NotNil(t, state) waitFor(t, 5*time.Second, func() bool { state.mu.Lock() defer state.mu.Unlock() @@ -258,7 +261,8 @@ func TestCrashThenExternalService_IsAdoptedWithoutRespawn(t *testing.T) { go func() { _ = external.Serve(ln) }() defer func() { _ = external.Close() - _, _ = ex.Status(m.Engine) + _, err := ex.Status(m.Engine) + require.NoError(t, err) }() require.NoError(t, ex.Start(context.Background(), m.Engine), "restart should adopt the external service") @@ -290,7 +294,8 @@ func TestStart_UsesRequestedPort(t *testing.T) { t.Cleanup(func() { _ = ex.Stop("fake") }) const want = 17777 require.NoError(t, ex.StartWith(context.Background(), "fake", startOpts{Port: want}), "start with port override") - st, _ := ex.Status("fake") + st, err := ex.Status("fake") + require.NoError(t, err) require.Equal(t, want, st.Port, "expected port") } @@ -316,8 +321,7 @@ func TestStatus_ReportsExternallyRunning(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) key := runtime.GOOS + "/" + runtime.GOARCH m := &Manifest{ @@ -334,7 +338,8 @@ func TestStatus_ReportsExternallyRunning(t *testing.T) { }, } ex := newTestExecutor(t, m) - st, _ := ex.Status("ext") // never started via the executor + st, err := ex.Status("ext") // never started via the executor + require.NoError(t, err) require.True(t, st.Installed, "expected externally-running engine adopted as installed and healthy (%v)", st) require.True(t, st.Running, "expected externally-running engine adopted as installed and healthy (%v)", st) require.True(t, st.Healthy, "expected externally-running engine adopted as installed and healthy (%v)", st) @@ -345,8 +350,7 @@ func TestStatusAtPort_AdoptsLegacyListener(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - legacyPort, _ := strconv.Atoi(u.Port()) + legacyPort := portOf(t, srv.URL) key := runtime.GOOS + "/" + runtime.GOARCH m := &Manifest{ @@ -376,8 +380,7 @@ func TestGetInstalled_AdoptsExternallyRunningEngineWithoutDetectPath(t *testing. w.WriteHeader(http.StatusOK) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) key := runtime.GOOS + "/" + runtime.GOARCH m := &Manifest{ @@ -408,16 +411,15 @@ func TestInstall_AdoptsExternalServiceWithoutDownloading(t *testing.T) { return } w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"version":"test"}`)) + writeTestResponse(t, w, []byte(`{"version":"test"}`)) })) defer engineSrv.Close() - engineURL, _ := url.Parse(engineSrv.URL) - enginePort, _ := strconv.Atoi(engineURL.Port()) + enginePort := portOf(t, engineSrv.URL) var downloads atomic.Int32 downloadSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { downloads.Add(1) - _, _ = w.Write([]byte("must-not-download")) + writeTestResponse(t, w, []byte("must-not-download")) })) defer downloadSrv.Close() @@ -461,13 +463,12 @@ func TestInstall_DoesNotOverwriteUnknownListener(t *testing.T) { http.NotFound(w, r) })) defer occupant.Close() - u, _ := url.Parse(occupant.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, occupant.URL) var downloads atomic.Int32 downloadSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { downloads.Add(1) - _, _ = w.Write([]byte("must-not-download")) + writeTestResponse(t, w, []byte("must-not-download")) })) defer downloadSrv.Close() @@ -485,7 +486,8 @@ func TestInstall_DoesNotOverwriteUnknownListener(t *testing.T) { ex := newTestExecutor(t, m) require.ErrorContains(t, ex.Install(context.Background(), m.Engine), "occupied", "install over an unknown listener error") require.Equal(t, int32(0), downloads.Load(), "unknown listener triggered") - st, _ := ex.Status(m.Engine) + st, err := ex.Status(m.Engine) + require.NoError(t, err) require.False(t, st.Installed, "unknown listener must not be adopted (%v)", st) require.False(t, st.Running, "unknown listener must not be adopted (%v)", st) } @@ -497,7 +499,7 @@ func TestCommandModeMissingCLI_InstallsDespiteLiveAPI(t *testing.T) { return } w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"object":"list","data":[]}`)) + writeTestResponse(t, w, []byte(`{"object":"list","data":[]}`)) })) defer srv.Close() port := portOf(t, srv.URL) @@ -527,7 +529,7 @@ func TestCommandModeMissingCLI_InstallsDespiteLiveAPI(t *testing.T) { func TestStatus_DoesNotAdoptFacadeOnDifferentConfiguredPort(t *testing.T) { facade := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/api/version" { - _, _ = w.Write([]byte(`{"version":"facade"}`)) + writeTestResponse(t, w, []byte(`{"version":"facade"}`)) return } http.NotFound(w, r) @@ -561,8 +563,7 @@ func TestAdoptedEngine_ReportsObservedLiveness(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(int(status.Load())) })) - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) key := runtime.GOOS + "/" + runtime.GOARCH m := &Manifest{ @@ -575,17 +576,20 @@ func TestAdoptedEngine_ReportsObservedLiveness(t *testing.T) { }}, } ex := newTestExecutor(t, m) - st, _ := ex.Status(m.Engine) + st, err := ex.Status(m.Engine) + require.NoError(t, err) require.True(t, st.Running, "initial external status (%v)", st) require.True(t, st.Healthy, "initial external status (%v)", st) status.Store(http.StatusServiceUnavailable) - st, _ = ex.Status(m.Engine) + st, err = ex.Status(m.Engine) + require.NoError(t, err) require.True(t, st.Running, "HTTP 503 is live-but-unhealthy (%v)", st) require.False(t, st.Healthy, "HTTP 503 is live-but-unhealthy (%v)", st) srv.Close() - st, _ = ex.Status(m.Engine) + st, err = ex.Status(m.Engine) + require.NoError(t, err) require.False(t, st.Installed, "closed external listener remained present (%v)", st) require.False(t, st.Running, "closed external listener remained present (%v)", st) require.False(t, st.Healthy, "closed external listener remained present (%v)", st) @@ -602,8 +606,7 @@ func TestProxyFacade_CannotIdentifyAsOllama(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) m := testEngineManifest(fakeEngineBin) m.Engine = "ollama" @@ -622,7 +625,9 @@ func TestProxyFacade_CannotIdentifyAsOllama(t *testing.T) { require.False(t, st.Healthy, "proxy facade was adopted: status (%v, %v)", st, err) require.ErrorContains(t, ex.Install(context.Background(), "ollama"), "occupied", "install over proxy facade error") require.ErrorContains(t, ex.Start(context.Background(), "ollama"), "occupied", "start over proxy facade error") - state, _ := ex.state("ollama") + state, err := ex.state("ollama") + require.NoError(t, err) + require.NotNil(t, state) state.mu.Lock() state.running = true // stale pre-transition state must not route an action through the facade state.mu.Unlock() @@ -646,8 +651,7 @@ func TestPreviouslyAdoptedForeignReplacement_FailsClosed(t *testing.T) { http.NotFound(w, r) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) m := testEngineManifest(fakeEngineBin) m.Engine = "ollama" @@ -703,7 +707,8 @@ func TestUninstall_TerminatesRunningInstance(t *testing.T) { reg.engines[m.Engine] = m ex := NewExecutor(reg, NewReporter(nil), func(string, any) {}, baseDir) require.NoError(t, ex.Uninstall(context.Background(), "fake"), "uninstall") - ok, _ := ex.Detect("fake") + ok, err := ex.Detect("fake") + require.NoError(t, err) require.False(t, ok, "engine still detected after uninstall") require.False(t, portServing(port), "expected uninstall to stop the running instance") } @@ -766,8 +771,7 @@ func TestUninstall_RefusesUnidentifiedLiveListener(t *testing.T) { srv := httptest.NewServer(http.NotFoundHandler()) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) m := testEngineManifest(managedBin) key := runtime.GOOS + "/" + runtime.GOARCH @@ -812,7 +816,7 @@ func TestDownload_VerifiesChecksumAndReportsHTTPFailures(t *testing.T) { good := hex.EncodeToString(sum[:]) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write(payload) + writeTestResponse(t, w, payload) })) defer srv.Close() @@ -823,7 +827,8 @@ func TestDownload_VerifiesChecksumAndReportsHTTPFailures(t *testing.T) { require.NoError(t, err, "download (ok)") defer os.Remove(p) require.Equal(t, ".ps1", filepath.Ext(p), "downloaded path (%v)", p) - got, _ := os.ReadFile(p) + got, err := os.ReadFile(p) + require.NoError(t, err) require.Equal(t, string(payload), string(got), "downloaded content mismatch") _, err = ex.download(ctx, "fake", &Fetch{URL: srv.URL + "/installer.ps1", SHA256: "deadbeef"}) @@ -867,10 +872,12 @@ func TestUninstall_RemovesInstalledEngine(t *testing.T) { reg := NewRegistry() reg.engines[m.Engine] = m ex := NewExecutor(reg, NewReporter(nil), func(string, any) {}, baseDir) - ok, _ := ex.Detect("fake") + ok, err := ex.Detect("fake") + require.NoError(t, err) require.True(t, ok, "expected installed before uninstall") require.NoError(t, ex.Uninstall(context.Background(), "fake"), "uninstall") - ok, _ = ex.Detect("fake") + ok, err = ex.Detect("fake") + require.NoError(t, err) require.False(t, ok, "expected not-installed after uninstall") } @@ -921,8 +928,7 @@ func TestCommandMode_StartsReportsHealthAndStopsEngine(t *testing.T) { } } }() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) key := runtime.GOOS + "/" + runtime.GOARCH m := &Manifest{ @@ -945,13 +951,15 @@ func TestCommandMode_StartsReportsHealthAndStopsEngine(t *testing.T) { require.NoError(t, ex.Start(context.Background(), "daemon"), "start") require.FileExists(t, startMarker, "expected the start command to have run") - st, _ := ex.Status("daemon") + st, err := ex.Status("daemon") + require.NoError(t, err) require.True(t, st.Running, "expected running+healthy (%v)", st) require.True(t, st.Healthy, "expected running+healthy (%v)", st) require.NoError(t, ex.Stop("daemon"), "stop") require.FileExists(t, stopMarker, "expected the stop command to have run") - st, _ = ex.Status("daemon") + st, err = ex.Status("daemon") + require.NoError(t, err) require.False(t, st.Running, "expected stopped (%v)", st) } @@ -976,8 +984,7 @@ func TestStart_AdoptsAlreadyRunning(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) key := runtime.GOOS + "/" + runtime.GOARCH m := &Manifest{ @@ -995,11 +1002,14 @@ func TestStart_AdoptsAlreadyRunning(t *testing.T) { } ex := newTestExecutor(t, m) require.NoError(t, ex.Start(context.Background(), "adopt"), "expected adoption of the already-serving instance") - st, _ := ex.Status("adopt") + st, err := ex.Status("adopt") + require.NoError(t, err) require.True(t, st.Installed, "expected running via adoption (%v)", st) require.True(t, st.Running, "expected running via adoption (%v)", st) require.True(t, st.Healthy, "expected running via adoption (%v)", st) - state, _ := ex.state("adopt") + state, err := ex.state("adopt") + require.NoError(t, err) + require.NotNil(t, state) state.mu.Lock() binPath := state.binPath state.mu.Unlock() diff --git a/services/nvpair-engine-manager/health_connections_test.go b/services/nvpair-engine-manager/health_connections_test.go index f6ebfa9c..f681358d 100644 --- a/services/nvpair-engine-manager/health_connections_test.go +++ b/services/nvpair-engine-manager/health_connections_test.go @@ -30,9 +30,10 @@ func TestProbeHTTP_ReusesConnections(t *testing.T) { } w.WriteHeader(status) if chunked { - _ = http.NewResponseController(w).Flush() + assert.NoError(t, http.NewResponseController(w).Flush()) } - _, _ = io.WriteString(w, `{"models":[],"status":"responding"}`) + _, err := io.WriteString(w, `{"models":[],"status":"responding"}`) + assert.NoError(t, err) })) ex := &Executor{client: client} probe := &Probe{HTTP: "http://127.0.0.1:{port}/api/version"} @@ -54,7 +55,8 @@ func TestProbeHTTP_MatchesJSONIdentity(t *testing.T) { t.Run(name, func(t *testing.T) { client, _ := testclient.New(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = io.WriteString(w, body) + _, err := io.WriteString(w, body) + assert.NoError(t, err, "write probe response") })) ex := &Executor{client: client} probe := &Probe{ diff --git a/services/nvpair-engine-manager/install_artifacts_test.go b/services/nvpair-engine-manager/install_artifacts_test.go index 52dbcc6c..6b21a036 100644 --- a/services/nvpair-engine-manager/install_artifacts_test.go +++ b/services/nvpair-engine-manager/install_artifacts_test.go @@ -59,7 +59,7 @@ func (r *installProgressRecorder) assertSingleTerminal(t *testing.T, want string func TestInstall_DownloadsAllNamedArtifactsBeforeRunning(t *testing.T) { payload := []byte("artifact") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write(payload) + writeTestResponse(t, w, payload) })) defer server.Close() @@ -87,7 +87,7 @@ func TestInstall_DownloadsAllNamedArtifactsBeforeRunning(t *testing.T) { func TestInstall_RejectsBadSecondArtifactBeforeCommand(t *testing.T) { payload := []byte("artifact") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write(payload) + writeTestResponse(t, w, payload) })) defer server.Close() @@ -112,10 +112,10 @@ func TestInstallCancellation_RemovesDownloadedArtifacts(t *testing.T) { secondStarted := make(chan struct{}, 1) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/server.zip" { - _, _ = w.Write(firstPayload) + writeTestResponse(t, w, firstPayload) return } - _, _ = w.Write([]byte("partial")) + writeTestResponse(t, w, []byte("partial")) if flusher, ok := w.(http.Flusher); ok { flusher.Flush() } diff --git a/services/nvpair-engine-manager/install_linux_test.go b/services/nvpair-engine-manager/install_linux_test.go index e3df1a1f..7dbf515a 100644 --- a/services/nvpair-engine-manager/install_linux_test.go +++ b/services/nvpair-engine-manager/install_linux_test.go @@ -40,7 +40,7 @@ func TestLlamaCPPInstall_PreservesShellArtifactArguments(t *testing.T) { http.NotFound(w, r) return } - _, _ = w.Write(payload) + writeTestResponse(t, w, payload) })) defer server.Close() diff --git a/services/nvpair-engine-manager/install_windows_test.go b/services/nvpair-engine-manager/install_windows_test.go index ffd0d176..2a603746 100644 --- a/services/nvpair-engine-manager/install_windows_test.go +++ b/services/nvpair-engine-manager/install_windows_test.go @@ -33,7 +33,7 @@ func TestLlamaCPPInstall_PreservesDestinationPathWithSpaces(t *testing.T) { http.NotFound(w, r) return } - _, _ = w.Write(payload) + writeTestResponse(t, w, payload) })) defer server.Close() diff --git a/services/nvpair-engine-manager/ipc_test.go b/services/nvpair-engine-manager/ipc_test.go index 35b1d271..5bab6fd8 100644 --- a/services/nvpair-engine-manager/ipc_test.go +++ b/services/nvpair-engine-manager/ipc_test.go @@ -34,7 +34,7 @@ func TestEngineManager_RespondsToRequestsOverIPC(t *testing.T) { defer conn.Close() frames := make(chan frame, 64) - go readFrames(conn, frames) + go readFrames(t, conn, frames) waitNotify(t, frames, "engine:ready", 5*time.Second) send(t, conn, 1, "engine:get-installed", nil) diff --git a/services/nvpair-engine-manager/launch_controls_test.go b/services/nvpair-engine-manager/launch_controls_test.go index d789be56..7427a3ab 100644 --- a/services/nvpair-engine-manager/launch_controls_test.go +++ b/services/nvpair-engine-manager/launch_controls_test.go @@ -209,7 +209,7 @@ func TestCORSOrigins_NormalizesEquivalentPolicies(t *testing.T) { test := func(name, input, want string) { t.Run(name, func(t *testing.T) { got, err := normalizeCORSOrigins(input) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, want, got) }) } diff --git a/services/nvpair-engine-manager/lifecycle_stop_test.go b/services/nvpair-engine-manager/lifecycle_stop_test.go index 521b3ba7..701b2f73 100644 --- a/services/nvpair-engine-manager/lifecycle_stop_test.go +++ b/services/nvpair-engine-manager/lifecycle_stop_test.go @@ -80,7 +80,8 @@ func TestStop_ReclaimsOurOrphanOnManagedPort(t *testing.T) { require.NoError(t, ex.Stop("fake"), "stop must reclaim our own orphan, got err") require.False(t, portServing(port), "orphan on our managed port must be terminated by stop") time.Sleep(1500 * time.Millisecond) // outlast a health interval - st, _ = ex.Status("fake") + st, err = ex.Status("fake") + require.NoError(t, err) require.False(t, st.Running, "engine must stay stopped after reclaim (%v)", st) require.False(t, st.Healthy, "engine must stay stopped after reclaim (%v)", st) enabled, known, err := ex.desired.get("fake") @@ -150,8 +151,7 @@ func TestStop_RejectsAdoptedProcessEngine(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) m := testEngineManifest(fakeEngineBin) p := m.Platforms[runtime.GOOS+"/"+runtime.GOARCH] @@ -170,7 +170,8 @@ func TestStop_RejectsAdoptedProcessEngine(t *testing.T) { } require.ErrorContains(t, ex.Stop("fake"), "external management", "stop error") - st, _ = ex.Status("fake") + st, err = ex.Status("fake") + require.NoError(t, err) require.True(t, st.Running, "rejected stop must keep the live engine running (%v)", st) require.True(t, st.Healthy, "rejected stop must keep the live engine running (%v)", st) select { @@ -186,8 +187,7 @@ func TestStop_ReconcilesAdoptedProcessAfterExternalStop(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })) - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) m := testEngineManifest(fakeEngineBin) p := m.Platforms[runtime.GOOS+"/"+runtime.GOARCH] p.Runtime.Port = port @@ -200,7 +200,8 @@ func TestStop_ReconcilesAdoptedProcessAfterExternalStop(t *testing.T) { srv.Close() require.NoError(t, ex.Stop("fake"), "retry after external stop") - st, _ = ex.Status("fake") + st, err = ex.Status("fake") + require.NoError(t, err) require.False(t, st.Running, "closed external endpoint must reconcile to stopped (%v)", st) require.False(t, st.Healthy, "closed external endpoint must reconcile to stopped (%v)", st) } @@ -215,8 +216,7 @@ func TestStop_KeepsAdoptedProcessRunningWhileEndpointIsUnhealthy(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer srv.Close() - u, _ := url.Parse(srv.URL) - port, _ := strconv.Atoi(u.Port()) + port := portOf(t, srv.URL) m := testEngineManifest(fakeEngineBin) p := m.Platforms[runtime.GOOS+"/"+runtime.GOARCH] p.Runtime.Port = port @@ -229,7 +229,8 @@ func TestStop_KeepsAdoptedProcessRunningWhileEndpointIsUnhealthy(t *testing.T) { unhealthy.Store(true) require.ErrorContains(t, ex.Stop("fake"), "external management", "stop error") - st, _ = ex.Status("fake") + st, err = ex.Status("fake") + require.NoError(t, err) require.True(t, st.Running, "an unhealthy but reachable endpoint must not report stopped (%v)", st) } @@ -243,7 +244,8 @@ func TestCommandStopFailure_KeepsLiveEngineRunning(t *testing.T) { require.ErrorContains(t, ex.Stop("command"), "stop command", "stop error") require.FileExists(t, marker, "stop command did not run") - st, _ := ex.Status("command") + st, err := ex.Status("command") + require.NoError(t, err) require.True(t, st.Running, "failed stop must keep the live engine running (%v)", st) require.True(t, st.Healthy, "failed stop must keep the live engine running (%v)", st) } @@ -270,7 +272,8 @@ func TestCommandStopFailure_SucceedsWhenEndpointIsConfirmedDown(t *testing.T) { case <-time.After(3 * time.Second): require.FailNow(t, "test endpoint did not close") } - st, _ := ex.Status("command") + st, err := ex.Status("command") + require.NoError(t, err) require.False(t, st.Running, "closed endpoint must report stopped (%v)", st) require.False(t, st.Healthy, "closed endpoint must report stopped (%v)", st) } @@ -278,12 +281,15 @@ func TestCommandStopFailure_SucceedsWhenEndpointIsConfirmedDown(t *testing.T) { func TestCommandStopWithoutReadinessProbe_UsesConfiguredCommand(t *testing.T) { marker := filepath.Join(t.TempDir(), "stop-attempted") ex := commandStopTestExecutor(t, "http://127.0.0.1:1", []string{fakeEngineBin, "touch", marker}, 1, false) - st, _ := ex.state("command") + st, err := ex.state("command") + require.NoError(t, err) + require.NotNil(t, st) st.plat.Runtime.Ready = nil require.NoError(t, ex.Stop("command"), "stop without readiness probe") require.FileExists(t, marker, "configured stop command did not run") - status, _ := ex.Status("command") + status, err := ex.Status("command") + require.NoError(t, err) require.False(t, status.Running, "legacy no-probe command stop must report stopped (%v)", status) require.False(t, status.Healthy, "legacy no-probe command stop must report stopped (%v)", status) } @@ -297,7 +303,8 @@ func TestCommandStop_RejectsSuccessWhileEndpointIsLive(t *testing.T) { ex := commandStopTestExecutor(t, srv.URL, []string{fakeEngineBin, "touch", marker}, 1, false) require.ErrorContains(t, ex.Stop("command"), "still serving", "stop error") - st, _ := ex.Status("command") + st, err := ex.Status("command") + require.NoError(t, err) require.True(t, st.Running, "live endpoint must remain reported running (%v)", st) require.True(t, st.Healthy, "live endpoint must remain reported running (%v)", st) } @@ -311,7 +318,8 @@ func TestCommandStop_RejectsSuccessWhileEndpointIsUnhealthy(t *testing.T) { ex := commandStopTestExecutor(t, srv.URL, []string{fakeEngineBin, "touch", marker}, 1, false) require.ErrorContains(t, ex.Stop("command"), "still serving", "stop error") - st, _ := ex.Status("command") + st, err := ex.Status("command") + require.NoError(t, err) require.True(t, st.Running, "an unhealthy but reachable endpoint must remain running (%v)", st) } @@ -321,7 +329,9 @@ func TestCommandStopFailure_RestartsHealthMonitoring(t *testing.T) { })) marker := filepath.Join(t.TempDir(), "stop-attempted") ex := commandStopTestExecutor(t, srv.URL, []string{fakeEngineBin, "touch", marker}, 1, false) - st, _ := ex.state("command") + st, err := ex.state("command") + require.NoError(t, err) + require.NotNil(t, st) st.plat.Runtime.Health = &Probe{HTTP: srv.URL, Status: http.StatusOK, IntervalS: 1} t.Cleanup(func() { st.mu.Lock() @@ -366,7 +376,8 @@ func TestCommandStop_ReportsStoppedOnlyAfterEndpointCloses(t *testing.T) { case <-time.After(3 * time.Second): require.FailNow(t, "test endpoint did not close") } - st, _ := ex.Status("command") + st, err := ex.Status("command") + require.NoError(t, err) require.False(t, st.Running, "closed endpoint must report stopped (%v)", st) require.False(t, st.Healthy, "closed endpoint must report stopped (%v)", st) } diff --git a/services/nvpair-engine-manager/live_pound_test.go b/services/nvpair-engine-manager/live_pound_test.go index 533440ac..054bd7af 100644 --- a/services/nvpair-engine-manager/live_pound_test.go +++ b/services/nvpair-engine-manager/live_pound_test.go @@ -12,6 +12,7 @@ package main import ( + "net" "os" "os/exec" "runtime" @@ -90,9 +91,13 @@ func TestLivePound_RepairsOllamaListenerAndLifecycle(t *testing.T) { // listenAddrs returns every LocalAddress with a listener on port. func listenAddrs(t *testing.T, port int) []string { t.Helper() - out, _ := exec.Command("powershell", "-NoProfile", "-Command", - "Get-NetTCPConnection -LocalPort "+strconv.Itoa(port)+" -State Listen -ErrorAction SilentlyContinue | "+ + // Filter after enumeration so an absent listener is an empty result, + // while failures to query the connection table still fail the test. + out, err := exec.Command("powershell", "-NoProfile", "-Command", + "Get-NetTCPConnection -ErrorAction Stop | "+ + "Where-Object { $_.LocalPort -eq "+strconv.Itoa(port)+" -and $_.State -eq 'Listen' } | "+ "Select-Object -ExpandProperty LocalAddress").Output() + require.NoError(t, err) var addrs []string for _, l := range strings.Split(strings.TrimSpace(string(out)), "\n") { if s := strings.TrimSpace(l); s != "" { @@ -102,6 +107,27 @@ func listenAddrs(t *testing.T, port int) []string { return addrs } +func TestListenAddrs_ReturnsEmptyAfterListenerCloses(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("listener-address assertions use Windows Get-NetTCPConnection") + } + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { + if err := listener.Close(); err != nil { + assert.ErrorIs(t, err, net.ErrClosed) + } + }) + _, portText, err := net.SplitHostPort(listener.Addr().String()) + require.NoError(t, err) + port, err := strconv.Atoi(portText) + require.NoError(t, err) + require.Contains(t, listenAddrs(t, port), "127.0.0.1") + + require.NoError(t, listener.Close()) + assert.Empty(t, listenAddrs(t, port)) +} + // isLoopbackOnly reports whether there is at least one listener and every // listener is a loopback address. func isLoopbackOnly(addrs []string) bool { diff --git a/services/nvpair-engine-manager/live_test.go b/services/nvpair-engine-manager/live_test.go index eee3e86d..3f508634 100644 --- a/services/nvpair-engine-manager/live_test.go +++ b/services/nvpair-engine-manager/live_test.go @@ -183,7 +183,9 @@ func TestLiveLMStudio_InstallsStartsAndUninstallsInCleanEnvironment(t *testing.T // The bundled manifest installs into (and uninstalls) the real // ~/.lmstudio. Refuse to run if one already exists, so we never delete // a user's pre-existing LM Studio. - if home, _ := os.UserHomeDir(); home != "" { + home, err := os.UserHomeDir() + require.NoError(t, err) + if home != "" { if _, err := os.Stat(filepath.Join(home, ".lmstudio")); err == nil { t.Skip("~/.lmstudio already exists; skipping so we don't uninstall a real LM Studio install") } @@ -230,7 +232,7 @@ func startManager(t *testing.T, env map[string]string) (chan frame, io.WriteClos for sc.Scan() { line := sc.Text() var f frame - _ = json.Unmarshal([]byte(line), &f) + assert.NoError(t, json.Unmarshal([]byte(line), &f)) t.Logf("[%6dms] %s", time.Since(start).Milliseconds(), line) frames <- f } diff --git a/services/nvpair-engine-manager/manager_test.go b/services/nvpair-engine-manager/manager_test.go index 9afd0ab5..75685f8a 100644 --- a/services/nvpair-engine-manager/manager_test.go +++ b/services/nvpair-engine-manager/manager_test.go @@ -65,7 +65,8 @@ func TestRunOp_StartsEngineAfterInstallation(t *testing.T) { id := json.RawMessage("1") m.runOp(context.Background(), &Message{JSONRPC: "2.0", ID: &id, Method: "engine:install", Params: json.RawMessage(`{"engine":"fake","start":true}`)}) - st, _ := ex.Status("fake") + st, err := ex.Status("fake") + require.NoError(t, err) require.True(t, st.Running, "expected running after install+autostart (%v)", st) } @@ -81,8 +82,10 @@ func TestStatusQueries_DoNotBlockMessageDispatchDuringEngineOperation(t *testing m := NewManager(NewCodec(managerConn), ex, nil) id := json.RawMessage("1") response := make(chan string, 1) + readErr := make(chan error, 1) go func() { - line, _ := bufio.NewReader(clientConn).ReadString('\n') + line, err := bufio.NewReader(clientConn).ReadString('\n') + readErr <- err response <- line }() @@ -112,6 +115,7 @@ func TestStatusQueries_DoNotBlockMessageDispatchDuringEngineOperation(t *testing st.opMu.Unlock() select { case line := <-response: + require.NoError(t, <-readErr) require.Contains(t, line, `"id":1`) case <-time.After(2 * time.Second): require.FailNow(t, "no response after the engine operation completed") diff --git a/services/nvpair-engine-manager/models_gate_test.go b/services/nvpair-engine-manager/models_gate_test.go index 66e7f068..964ccb55 100644 --- a/services/nvpair-engine-manager/models_gate_test.go +++ b/services/nvpair-engine-manager/models_gate_test.go @@ -23,6 +23,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/clustertrust" @@ -34,8 +35,10 @@ func mintLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "genkey") - serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - san, _ := url.Parse("urn:nvpair:node:" + uuid) + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + require.NoError(t, err) + san, err := url.Parse("urn:nvpair:node:" + uuid) + require.NoError(t, err) tmpl := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: uuid}, @@ -48,7 +51,8 @@ func mintLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) require.NoError(t, err, "create cert") - keyDER, _ := x509.MarshalPKCS8PrivateKey(priv) + keyDER, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err) return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) } @@ -60,20 +64,28 @@ func clusterDirFor(t *testing.T, certPEM, keyPEM []byte, pins map[string][]byte) dir := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(dir, "node.crt"), certPEM, 0o644)) require.NoError(t, os.WriteFile(filepath.Join(dir, "node.key"), keyPEM, 0o600)) - admission, _ := json.Marshal(map[string]any{"counter": 1, "activated": 1, "clusterId": "gate-test", "epoch": 1}) + admission, err := json.Marshal(map[string]any{"counter": 1, "activated": 1, "clusterId": "gate-test", "epoch": 1}) + require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(dir, "admission.json"), admission, 0o600)) trusted := filepath.Join(dir, "trusted") require.NoError(t, os.MkdirAll(trusted, 0o700)) for uuid, cert := range pins { - body, _ := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(cert)}) + body, err := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(cert)}) + require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(trusted, uuid+".json"), body, 0o600)) } return dir } -func okHandler() http.Handler { +func writeTestResponse(t *testing.T, w http.ResponseWriter, body []byte) { + t.Helper() + _, err := w.Write(body) + assert.NoError(t, err) +} + +func okHandler(t *testing.T) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte(`{"models":["llama"]}`)) + writeTestResponse(t, w, []byte(`{"models":["llama"]}`)) }) } @@ -82,7 +94,7 @@ func okHandler() http.Handler { // a standalone machine reads its own inventory this way — and must refuse a LAN // caller, which is the leak this gate closes. func TestModelSurface_RestrictsPlaintextToLoopback(t *testing.T) { - h := loopbackOnly(okHandler()) + h := loopbackOnly(okHandler(t)) test := func(name, remote string, wantStatus int) { t.Run(name, func(t *testing.T) { @@ -113,7 +125,7 @@ func TestModelSurface_RequiresPinnedPeerForEveryClusterRequest(t *testing.T) { peerMesh := clustertrust.Open(clusterDirFor(t, peerCert, peerKey, map[string][]byte{"uuid-self": selfCert})) strangerMesh := clustertrust.Open(clusterDirFor(t, strangerCert, strangerKey, map[string][]byte{"uuid-self": selfCert})) - srv := httptest.NewUnstartedServer(requirePinnedPeer(selfMesh, okHandler())) + srv := httptest.NewUnstartedServer(requirePinnedPeer(selfMesh, okHandler(t))) srv.TLS = selfMesh.ServerTLSConfig() srv.StartTLS() defer srv.Close() @@ -146,7 +158,7 @@ func TestModelSurface_RequiresPinnedPeerForEveryClusterRequest(t *testing.T) { mesh = clustertrust.Open(t.TempDir()) } rec := httptest.NewRecorder() - requirePinnedPeer(mesh, okHandler())(rec, httptest.NewRequest(http.MethodGet, modelsPath, nil)) + requirePinnedPeer(mesh, okHandler(t))(rec, httptest.NewRequest(http.MethodGet, modelsPath, nil)) require.Equal(t, http.StatusForbidden, rec.Code) }) } diff --git a/services/nvpair-engine-manager/models_test.go b/services/nvpair-engine-manager/models_test.go index 305a7059..cc738b28 100644 --- a/services/nvpair-engine-manager/models_test.go +++ b/services/nvpair-engine-manager/models_test.go @@ -204,11 +204,12 @@ func setLoaded(t *testing.T, ex *Executor, names []string) { t.Helper() st, err := ex.Status("fake") require.NoError(t, err, "status") - body, _ := json.Marshal(map[string][]string{"names": names}) + body, err := json.Marshal(map[string][]string{"names": names}) + require.NoError(t, err) url := fmt.Sprintf("http://127.0.0.1:%d/testctl/loaded", st.Port) resp, err := http.Post(url, "application/json", bytes.NewReader(body)) require.NoError(t, err, "set loaded") - _ = resp.Body.Close() + require.NoError(t, resp.Body.Close()) } // TestModelsResultLoaded covers the loaded surface: a running engine that diff --git a/services/nvpair-engine-manager/modelsdir_test.go b/services/nvpair-engine-manager/modelsdir_test.go index d86c8778..db2f1dff 100644 --- a/services/nvpair-engine-manager/modelsdir_test.go +++ b/services/nvpair-engine-manager/modelsdir_test.go @@ -180,7 +180,7 @@ func TestRemoveTreePreserving_KeepsASymlinkedStore(t *testing.T) { assert.FileExists(t, model, "deleted the directory the store links to") _, err := os.Lstat(store) - assert.NoError(t, err, "removed the store's link") + require.NoError(t, err, "removed the store's link") _, err = os.Stat(filepath.Dir(binary)) assert.ErrorIs(t, err, os.ErrNotExist, "the engine's own files survived") } @@ -251,7 +251,7 @@ func TestRemoveTreePreserving_RefusesInsideTheStore(t *testing.T) { require.NoError(t, os.MkdirAll(inside, 0o755)) assert.Error(t, removeTreePreserving(inside, store), "expected an error when the target is inside the model store") _, err := os.Stat(inside) - assert.NoError(t, err, "a refused removal still deleted %q", inside) + require.NoError(t, err, "a refused removal still deleted %q", inside) } // TestRemoveTreePreservingUnlinksSymlinkedTarget checks a symlinked target is @@ -273,7 +273,7 @@ func TestRemoveTreePreserving_UnlinksSymlinkedTarget(t *testing.T) { _, err := os.Lstat(link) assert.ErrorIs(t, err, os.ErrNotExist, "the link survived") _, err = os.Stat(filepath.Join(victim, "keep-me")) - assert.NoError(t, err, "followed the link and deleted the real directory's contents") + require.NoError(t, err, "followed the link and deleted the real directory's contents") } // TestRemoveTreePreservingIsBestEffort pins that one undeletable entry does not @@ -293,7 +293,7 @@ func TestRemoveTreePreserving_ReportsUndeletableEntriesAndRemovesOthers(t *testi // Read-only parent: the child cannot be unlinked, so "locked" fails while // "bin" — which sorts after it — must still go. require.NoError(t, os.Chmod(locked, 0o500)) - t.Cleanup(func() { _ = os.Chmod(locked, 0o700) }) + t.Cleanup(func() { assert.NoError(t, os.Chmod(locked, 0o700), "restore directory permissions") }) err := removeTreePreserving(engineRoot, filepath.Join(engineRoot, "models")) assert.Error(t, err, "expected the undeletable entry to be reported") @@ -302,5 +302,5 @@ func TestRemoveTreePreserving_ReportsUndeletableEntriesAndRemovesOthers(t *testi } func TestRemoveTreePreserving_SucceedsWhenTargetIsMissing(t *testing.T) { - assert.NoError(t, removeTreePreserving(filepath.Join(t.TempDir(), "absent"), ""), "removing an absent path should be a no-op") + require.NoError(t, removeTreePreserving(filepath.Join(t.TempDir(), "absent"), ""), "removing an absent path should be a no-op") } diff --git a/services/nvpair-engine-manager/portoverride_test.go b/services/nvpair-engine-manager/portoverride_test.go index 0437954d..fee5ada8 100644 --- a/services/nvpair-engine-manager/portoverride_test.go +++ b/services/nvpair-engine-manager/portoverride_test.go @@ -99,7 +99,8 @@ func TestPersistPort_RefusesMalformedOverrideWithoutClobbering(t *testing.T) { got, err := os.ReadFile(path) require.NoError(t, err, "read invalid override") require.Equal(t, data, string(got), "invalid override changed") - status, _ := ex.Status("ollama") + status, err := ex.Status("ollama") + require.NoError(t, err) require.Equal(t, 11434, status.Port, "failed persistence changed runtime port (%v)", status) }) } diff --git a/services/nvpair-engine-manager/progress_test.go b/services/nvpair-engine-manager/progress_test.go index 14fe639d..d6e73312 100644 --- a/services/nvpair-engine-manager/progress_test.go +++ b/services/nvpair-engine-manager/progress_test.go @@ -132,7 +132,7 @@ func TestInstallFlow_ReportsOnlyMeasurablePercents(t *testing.T) { payload := []byte("engine payload") sum := sha256.Sum256(payload) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write(payload) + writeTestResponse(t, w, payload) })) defer srv.Close() bin := filepath.Join(t.TempDir(), "engine"+exeExt()) diff --git a/services/nvpair-engine-manager/provenance_test.go b/services/nvpair-engine-manager/provenance_test.go index 0b4a5e20..ecf8d2c3 100644 --- a/services/nvpair-engine-manager/provenance_test.go +++ b/services/nvpair-engine-manager/provenance_test.go @@ -154,7 +154,7 @@ func TestUninstallManaged_CarriesOnPastAPartialRemoval(t *testing.T) { // The engine's bin directory cannot be emptied, so the binary survives and // the engine is still detected. Its cache can be, and goes. require.NoError(t, os.Chmod(filepath.Dir(stuckBinary), 0o500)) - t.Cleanup(func() { _ = os.Chmod(filepath.Dir(stuckBinary), 0o700) }) + t.Cleanup(func() { assert.NoError(t, os.Chmod(filepath.Dir(stuckBinary), 0o700), "restore directory permissions") }) stuck := vendorEngineManifest(stuckRoot, filepath.Join(stuckRoot, "models"), []string{stuckRoot}) stuck.Engine = "stuck" diff --git a/services/nvpair-engine-manager/remediation_test.go b/services/nvpair-engine-manager/remediation_test.go index 233a0562..194b667a 100644 --- a/services/nvpair-engine-manager/remediation_test.go +++ b/services/nvpair-engine-manager/remediation_test.go @@ -64,25 +64,26 @@ func capturingExecutor(t *testing.T, m *Manifest) (*Executor, *captured) { func TestDownload_AllowsManifestWithoutChecksum(t *testing.T) { payload := []byte("unpinned engine bytes") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write(payload) + writeTestResponse(t, w, payload) })) defer srv.Close() ex := newTestExecutor(t, testEngineManifest(fakeEngineBin)) p, err := ex.download(context.Background(), "fake", &Fetch{URL: srv.URL}) // no SHA256 require.NoError(t, err, "unpinned download should succeed") defer os.Remove(p) - got, _ := os.ReadFile(p) + got, err := os.ReadFile(p) + require.NoError(t, err) require.Equal(t, string(payload), string(got), "downloaded content mismatch") } // TestValidateDownloadURL pins the HTTPS-only policy (loopback http is the // only plaintext exception — used by the live tests and a LAN mirror). func TestValidateDownloadURL_RejectsUnsafeDownloadSchemes(t *testing.T) { - assert.NoError(t, validateDownloadURL("https://ollama.com/x.zip")) - assert.NoError(t, validateDownloadURL("https://example.com/y")) - assert.NoError(t, validateDownloadURL("http://127.0.0.1:8080/x")) - assert.NoError(t, validateDownloadURL("http://localhost/x")) - assert.NoError(t, validateDownloadURL("http://[::1]:9/x")) + require.NoError(t, validateDownloadURL("https://ollama.com/x.zip")) + require.NoError(t, validateDownloadURL("https://example.com/y")) + require.NoError(t, validateDownloadURL("http://127.0.0.1:8080/x")) + require.NoError(t, validateDownloadURL("http://localhost/x")) + require.NoError(t, validateDownloadURL("http://[::1]:9/x")) assert.Error(t, validateDownloadURL("http://example.com/x")) assert.Error(t, validateDownloadURL("http://10.0.0.5/x")) assert.Error(t, validateDownloadURL("ftp://h/y")) @@ -115,7 +116,8 @@ func TestUninstall_RetriesFailedCommandWithinBudget(t *testing.T) { // the retry behavior under test. require.NoError(t, writeInstallMarker(filepath.Join(ex.baseDir, "fake"), "fake")) require.ErrorContains(t, ex.Uninstall(context.Background(), "fake"), "after 3 attempts", "expected uninstall failure after 3 attempts") - data, _ := os.ReadFile(marker) + data, err := os.ReadFile(marker) + require.NoError(t, err) require.Len(t, data, 3, "expected 3 uninstall attempts, marker has") } @@ -132,7 +134,8 @@ func portOf(t *testing.T, rawURL string) int { t.Helper() u, err := url.Parse(rawURL) require.NoError(t, err) - p, _ := strconv.Atoi(u.Port()) + p, err := strconv.Atoi(u.Port()) + require.NoError(t, err) return p } @@ -193,7 +196,9 @@ func TestReadinessTimeout_DoesNotReportSpuriousProcessExit(t *testing.T) { // Give the watcher a chance to (wrongly) fire before asserting silence. time.Sleep(400 * time.Millisecond) require.False(t, hasErr(ex.Errors(), exitedID("slow")), "must NOT report 'exited unexpectedly' for a readiness timeout") - state, _ := ex.state("slow") + state, err := ex.state("slow") + require.NoError(t, err) + require.NotNil(t, state) state.mu.Lock() proc, running := state.proc, state.running state.mu.Unlock() @@ -238,16 +243,20 @@ func TestConcurrentStart_IsSerialized(t *testing.T) { var wg sync.WaitGroup for i := 0; i < 8; i++ { wg.Add(1) - go func() { defer wg.Done(); _ = ex.Start(context.Background(), "fake") }() + go func() { + defer wg.Done() + assert.NoError(t, ex.Start(context.Background(), "fake")) + }() } wg.Wait() - st, _ := ex.Status("fake") + st, err := ex.Status("fake") + require.NoError(t, err) require.True(t, st.Running, "expected running after concurrent starts (%v)", st) } func TestInstall_ReportsChecksumMismatch(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write([]byte("payload")) + writeTestResponse(t, w, []byte("payload")) })) defer srv.Close() m := &Manifest{ @@ -304,10 +313,15 @@ func TestHealthFlip_ReportsAndClears(t *testing.T) { func TestEngineLifecycle_ReportsUnexpectedExit(t *testing.T) { ex, _ := capturingExecutor(t, testEngineManifest(fakeEngineBin)) require.NoError(t, ex.Start(context.Background(), "fake"), "start") - st, _ := ex.Status("fake") + st, err := ex.Status("fake") + require.NoError(t, err) // The /exit route makes the fake engine os.Exit(1) — a crash. _, _ = http.Get(fmt.Sprintf("http://127.0.0.1:%d/exit", st.Port)) - waitFor(t, 6*time.Second, func() bool { s, _ := ex.Status("fake"); return !s.Running }) + waitFor(t, 6*time.Second, func() bool { + s, err := ex.Status("fake") + require.NoError(t, err) + return !s.Running + }) require.True(t, hasErr(ex.Errors(), exitedID("fake")), "expected an 'exited' error after a crash") } @@ -324,7 +338,8 @@ func TestRestart_StopsAndStartsManagedProcess(t *testing.T) { t.Cleanup(func() { _ = ex.Stop("fake") }) require.NoError(t, ex.Start(context.Background(), "fake"), "start") require.NoError(t, ex.Restart(context.Background(), "fake"), "restart") - st, _ := ex.Status("fake") + st, err := ex.Status("fake") + require.NoError(t, err) require.True(t, st.Running, "expected running+healthy after restart (%v)", st) require.True(t, st.Healthy, "expected running+healthy after restart (%v)", st) } @@ -397,7 +412,7 @@ func TestDownload_RejectsResponseExceedingSizeLimit(t *testing.T) { t.Cleanup(func() { maxDownloadBytes = old }) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write(make([]byte, 256)) // larger than the cap + writeTestResponse(t, w, make([]byte, 256)) // larger than the cap })) defer srv.Close() diff --git a/services/nvpair-engine-manager/remoteclient_test.go b/services/nvpair-engine-manager/remoteclient_test.go index e480b7af..36f8939b 100644 --- a/services/nvpair-engine-manager/remoteclient_test.go +++ b/services/nvpair-engine-manager/remoteclient_test.go @@ -19,7 +19,7 @@ func TestRemoteStart_UsesReadinessHeaderBudget(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { time.Sleep(100 * time.Millisecond) w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"engine":"remote-only","running":true,"healthy":true}`)) + writeTestResponse(t, w, []byte(`{"engine":"remote-only","running":true,"healthy":true}`)) })) defer srv.Close() @@ -42,7 +42,7 @@ func TestRemoteModelOperations_AllowReadinessResponseHeaderBudget(t *testing.T) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { time.Sleep(100 * time.Millisecond) w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"ok":true}`)) + writeTestResponse(t, w, []byte(`{"ok":true}`)) })) defer srv.Close() @@ -84,7 +84,7 @@ func TestRemoteStartHeaderWait_RemainsBounded(t *testing.T) { release := make(chan struct{}) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { <-release - _, _ = w.Write([]byte(`{"engine":"ollama"}`)) + writeTestResponse(t, w, []byte(`{"engine":"ollama"}`)) })) defer func() { close(release) diff --git a/services/nvpair-engine-manager/settings_test.go b/services/nvpair-engine-manager/settings_test.go index 964664e7..764e0b3e 100644 --- a/services/nvpair-engine-manager/settings_test.go +++ b/services/nvpair-engine-manager/settings_test.go @@ -617,7 +617,7 @@ func TestSavedLaunch_OverridesManifestEnvironmentLiterally(t *testing.T) { env := []string{"LD_LIBRARY_PATH=" + value} rt.LaunchEnv = env launch, err := resolveProcessLaunch(rt, "engine", map[string]string{"install_dir": "/default", "host": "127.0.0.1", "port": "12345"}) - assert.NoError(t, err, "saved launch environment") + require.NoError(t, err, "saved launch environment") assert.Equal(t, value, launch.Env["LD_LIBRARY_PATH"], "saved launch environment") }) } @@ -650,7 +650,7 @@ func TestSettings_SwapsLiveServerAndProxyPorts(t *testing.T) { if err != nil { return err } - _ = proxy.Close() + require.NoError(t, proxy.Close()) proxy = next return nil }) diff --git a/services/nvpair-engine-manager/settingsremote_test.go b/services/nvpair-engine-manager/settingsremote_test.go index d9e98ef1..ff2957f3 100644 --- a/services/nvpair-engine-manager/settingsremote_test.go +++ b/services/nvpair-engine-manager/settingsremote_test.go @@ -38,12 +38,15 @@ func settingsMesh(t *testing.T, id string) (string, []byte) { dir := t.TempDir() pub, key, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err) - uri, _ := url.Parse("urn:nvpair:node:" + id) + uri, err := url.Parse("urn:nvpair:node:" + id) + require.NoError(t, err) + require.NotNil(t, uri) cert := &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: id}, URIs: []*url.URL{uri}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}} der, err := x509.CreateCertificate(rand.Reader, cert, cert, pub, key) require.NoError(t, err) certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) - keyDER, _ := x509.MarshalPKCS8PrivateKey(key) + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + require.NoError(t, err) for name, data := range map[string][]byte{"node.crt": certPEM, "node.key": pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})} { require.NoError(t, os.WriteFile(filepath.Join(dir, name), data, 0600)) } @@ -53,7 +56,8 @@ func settingsPin(t *testing.T, dir, id string, cert []byte) { t.Helper() path := filepath.Join(dir, "trusted") require.NoError(t, os.MkdirAll(path, 0700)) - data, _ := json.Marshal(map[string]string{"nodeUuid": id, "certPem": string(cert)}) + data, err := json.Marshal(map[string]string{"nodeUuid": id, "certPem": string(cert)}) + require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(path, id+".json"), data, 0600)) } @@ -110,8 +114,9 @@ func TestPairedSettings_PushesChangesAndRevokesObservers(t *testing.T) { snapshot.Settings = p.Settings exec.settingsHub.Publish([]settings.Snapshot{snapshot}) } - data, _ := json.Marshal(snapshot) - return data, nil + data, err := json.Marshal(snapshot) + assert.NoError(t, err) + return data, err } server := httptest.NewUnstartedServer((&controlServer{exec: exec, mesh: mesh}).mux()) server.TLS = mesh.ServerTLSConfig() @@ -129,7 +134,9 @@ func TestPairedSettings_PushesChangesAndRevokesObservers(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() open := func(cl *http.Client) (*http.Response, *bufio.Scanner) { - req, _ := http.NewRequestWithContext(ctx, "GET", server.URL+settingsPath+"events", nil) + req, err := http.NewRequestWithContext(ctx, "GET", server.URL+settingsPath+"events", nil) + require.NoError(t, err) + require.NotNil(t, req) res, err := cl.Do(req) require.NoError(t, err) t.Cleanup(func() { _ = res.Body.Close() }) @@ -152,10 +159,11 @@ func TestPairedSettings_PushesChangesAndRevokesObservers(t *testing.T) { read(scanB, 1) read(scanC, 1) config := settings.Config{ServerPort: 12002, ProxyPort: 12003, LaunchText: "managed serve --parallel 2"} - body, _ := json.Marshal(settings.Request{NodeID: "forwarding-forbidden", Engine: "ollama", Settings: config}) + body, err := json.Marshal(settings.Request{NodeID: "forwarding-forbidden", Engine: "ollama", Settings: config}) + require.NoError(t, err) response, err := clientB.Post(server.URL+settingsPath+"apply", "application/json", bytes.NewReader(body)) require.NoError(t, err) - _ = response.Body.Close() + require.NoError(t, response.Body.Close()) require.Equal(t, 200, response.StatusCode, "paired mutation rejected") read(scanB, 2) read(scanC, 2) @@ -165,7 +173,7 @@ func TestPairedSettings_PushesChangesAndRevokesObservers(t *testing.T) { require.Equal(t, "b", seen, "caller not authenticated") response, err = client(stranger).Post(server.URL+settingsPath+"apply", "application/json", bytes.NewReader(body)) require.NoError(t, err) - _ = response.Body.Close() + require.NoError(t, response.Body.Close()) require.Equal(t, 403, response.StatusCode, "unpinned mutation accepted") require.NoError(t, os.Remove(filepath.Join(a, "trusted", "c.json"))) mesh.Refresh() @@ -203,10 +211,11 @@ func TestSettingsRemoteOnlyCORS_IsLocalOnly(t *testing.T) { t.Cleanup(transport.CloseIdleConnections) peer := &http.Client{Transport: transport} post := func(request settings.Request) int { - body, _ := json.Marshal(request) + body, err := json.Marshal(request) + require.NoError(t, err) response, err := peer.Post(server.URL+settingsPath+"apply", "application/json", bytes.NewReader(body)) require.NoError(t, err) - _ = response.Body.Close() + require.NoError(t, response.Body.Close()) return response.StatusCode } arguments := settingsRequest(t, exec) @@ -238,16 +247,17 @@ func TestSettingsRelay_CorrelatesResponsesAndCleansUpCancelledRequests(t *testin sent := make(chan settings.Relay, 2) canceled := make(chan string, 1) relay := &settingsRelay{send: func(method string, value any) error { - data, _ := json.Marshal(value) + data, err := json.Marshal(value) + assert.NoError(t, err) if method == "engine:settings-request" { var p settings.Relay - _ = json.Unmarshal(data, &p) + assert.NoError(t, json.Unmarshal(data, &p)) sent <- p } else { var p struct { ID string `json:"id"` } - _ = json.Unmarshal(data, &p) + assert.NoError(t, json.Unmarshal(data, &p)) canceled <- p.ID } return nil @@ -265,7 +275,8 @@ func TestSettingsRelay_CorrelatesResponsesAndCleansUpCancelledRequests(t *testin } first, second := <-sent, <-sent for _, request := range []settings.Relay{second, first} { - data, _ := json.Marshal(settingsReply{ID: request.ID, Result: json.RawMessage(`"` + request.Request.Engine + `"`)}) + data, err := json.Marshal(settingsReply{ID: request.ID, Result: json.RawMessage(`"` + request.Request.Engine + `"`)}) + require.NoError(t, err) relay.reply(data) } got := map[string]bool{<-results: true, <-results: true} diff --git a/services/nvpair-engine-manager/shutdown_test.go b/services/nvpair-engine-manager/shutdown_test.go index 186a85cc..6170b823 100644 --- a/services/nvpair-engine-manager/shutdown_test.go +++ b/services/nvpair-engine-manager/shutdown_test.go @@ -36,7 +36,8 @@ func TestStopAll_StopsEveryEngine(t *testing.T) { ports := make([]int, len(names)) for i, name := range names { require.NoError(t, ex.Start(ctx, name), "start (%v)", name) - st, _ := ex.Status(name) + st, err := ex.Status(name) + require.NoError(t, err) require.True(t, st.Running, " (%v, %v)", name, st) require.NotEqual(t, 0, st.Port, " (%v, %v)", name, st) ports[i] = st.Port diff --git a/services/nvpair-errors/cluster_mtls_test.go b/services/nvpair-errors/cluster_mtls_test.go index 61c6e122..d11ecda1 100644 --- a/services/nvpair-errors/cluster_mtls_test.go +++ b/services/nvpair-errors/cluster_mtls_test.go @@ -36,8 +36,10 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "genkey") - serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - uri, _ := url.Parse("urn:nvpair:node:" + uuid) + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + require.NoError(t, err, "generate certificate serial") + uri, err := url.Parse("urn:nvpair:node:" + uuid) + require.NoError(t, err, "parse certificate URI") tmpl := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: uuid}, @@ -50,7 +52,8 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) require.NoError(t, err, "create cert") - keyDER, _ := x509.MarshalPKCS8PrivateKey(priv) + keyDER, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err, "marshal private key") certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) return certPEM, keyPEM @@ -66,7 +69,8 @@ func setupNode(t *testing.T, certPEM, keyPEM []byte, pins map[string][]byte) str td := filepath.Join(dir, "trusted") require.NoError(t, os.MkdirAll(td, 0o700)) for uuid, pcert := range pins { - body, _ := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(pcert)}) + body, err := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(pcert)}) + require.NoError(t, err, "marshal peer pin") require.NoError(t, os.WriteFile(filepath.Join(td, uuid+".json"), body, 0o600)) } return dir @@ -124,7 +128,8 @@ func TestErrorsPeerSync_RequiresPinnedMutualTLS(t *testing.T) { srv.StartTLS() defer srv.Close() - body, _ := json.Marshal(errors.SyncEnvelope{NodeID: "uuid-b"}) + body, err := json.Marshal(errors.SyncEnvelope{NodeID: "uuid-b"}) + require.NoError(t, err, "marshal sync envelope") push := func(m *clustertrust.Mesh, peerUUID string) (int, error) { cfg, ok := m.ClientTLSConfig(peerUUID) if !ok { diff --git a/services/nvpair-errors/manager_test.go b/services/nvpair-errors/manager_test.go index 49a69e03..27ffc80f 100644 --- a/services/nvpair-errors/manager_test.go +++ b/services/nvpair-errors/manager_test.go @@ -67,15 +67,20 @@ func newTestManager(t *testing.T) (*Manager, *captureRW) { return m, rw } -func requestMessage(id int, method string, params any) *Message { - idData, _ := json.Marshal(id) +func requestMessage(t *testing.T, id int, method string, params any) *Message { + t.Helper() + idData, err := json.Marshal(id) + require.NoError(t, err, "marshal request ID") idRaw := json.RawMessage(idData) - paramsRaw, _ := json.Marshal(params) + paramsRaw, err := json.Marshal(params) + require.NoError(t, err, "marshal request params") return &Message{JSONRPC: "2.0", ID: &idRaw, Method: method, Params: paramsRaw} } -func notificationMessage(method string, params any) *Message { - paramsRaw, _ := json.Marshal(params) +func notificationMessage(t *testing.T, method string, params any) *Message { + t.Helper() + paramsRaw, err := json.Marshal(params) + require.NoError(t, err, "marshal notification params") return &Message{JSONRPC: "2.0", Method: method, Params: paramsRaw} } @@ -141,13 +146,15 @@ func decodeResult[T any](t *testing.T, msg Message) T { return result } -func responseWithID(id int) func(Message) bool { +func responseWithID(t *testing.T, id int) func(Message) bool { + t.Helper() return func(msg Message) bool { if msg.ID == nil { return false } var got int - return json.Unmarshal(*msg.ID, &got) == nil && got == id + require.NoError(t, json.Unmarshal(*msg.ID, &got), "decode response ID") + return got == id } } @@ -156,17 +163,17 @@ func responseWithID(id int) func(Message) bool { // Anything that produces an RPC error fails via decodeResult. func callAndDecode[T any](t *testing.T, m *Manager, rw *captureRW, id int, method string, params any) T { t.Helper() - m.handleMessage(requestMessage(id, method, params)) + m.handleMessage(requestMessage(t, id, method, params)) resp := readResponseFrame(t, rw) - require.True(t, responseWithID(id)(resp), "response id mismatch: (%v, %v)", resp, id) + require.True(t, responseWithID(t, id)(resp), "response id mismatch: (%v, %v)", resp, id) return decodeResult[T](t, resp) } func callExpectError(t *testing.T, m *Manager, rw *captureRW, id int, method string, params any, wantCode int) *RPCError { t.Helper() - m.handleMessage(requestMessage(id, method, params)) + m.handleMessage(requestMessage(t, id, method, params)) resp := readResponseFrame(t, rw) - require.True(t, responseWithID(id)(resp), "response id mismatch: (%v, %v)", resp, id) + require.True(t, responseWithID(t, id)(resp), "response id mismatch: (%v, %v)", resp, id) require.NotNil(t, resp.Error, "expected error (%v)", wantCode) require.Equal(t, wantCode, resp.Error.Code, "error code") return resp.Error @@ -197,7 +204,7 @@ func TestReportRequest_UpsertsAndEmitsUpdate(t *testing.T) { m, rw := newTestManager(t) e := sampleError("ollama-proxy:upstream-unreachable:node-a", 1000, "node-a unreachable") - m.handleMessage(requestMessage(1, "errors:report", e)) + m.handleMessage(requestMessage(t, 1, "errors:report", e)) resp := readResponseFrame(t, rw) require.Nil(t, resp.Error, "errors:report returned error") @@ -221,7 +228,7 @@ func TestReportNotification_UpsertsAndEmitsUpdate(t *testing.T) { m, rw := newTestManager(t) e := sampleError("manual-nodes:probe-failed:peer-1", 5000, "peer-1 probe failed") - m.handleMessage(notificationMessage("errors:report", e)) + m.handleMessage(notificationMessage(t, "errors:report", e)) expectNotification(t, rw, "errors:update") assertNoResponse(t, rw) @@ -241,15 +248,15 @@ func TestUpsert_PrefersHighestTimestamp(t *testing.T) { id := "ollama-local:not-running" // Initial report at ts=1000 wins. - m.handleMessage(notificationMessage("errors:report", sampleError(id, 1000, "v1"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 1000, "v1"))) expectNotification(t, rw, "errors:update") // Older ts=500 must be dropped — no update push. - m.handleMessage(notificationMessage("errors:report", sampleError(id, 500, "v0-stale"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 500, "v0-stale"))) assertNoNotification(t, rw) // Newer ts=2000 wins — update push fires. - m.handleMessage(notificationMessage("errors:report", sampleError(id, 2000, "v2"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 2000, "v2"))) expectNotification(t, rw, "errors:update") list := callAndDecode[[]ServiceError](t, m, rw, 1, "errors:get-initial", nil) @@ -266,10 +273,10 @@ func TestUpsert_ReplacesEntryAtEqualTimestamp(t *testing.T) { m, rw := newTestManager(t) id := "ollama-local:install-failed" - m.handleMessage(notificationMessage("errors:report", sampleError(id, 1000, "first"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 1000, "first"))) expectNotification(t, rw, "errors:update") - m.handleMessage(notificationMessage("errors:report", sampleError(id, 1000, "second"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 1000, "second"))) update := expectNotification(t, rw, "errors:update") got := decodeResult[[]ServiceError](t, Message{Result: update.Params}) require.Len(t, got, 1, "equal-ts upsert") @@ -283,10 +290,10 @@ func TestClear_RemovesEntryAndEmitsUpdate(t *testing.T) { m, rw := newTestManager(t) id := "supervisor:subprocess-crashed:nvpair-node-info" - m.handleMessage(notificationMessage("errors:report", sampleError(id, 1000, "crashed"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 1000, "crashed"))) expectNotification(t, rw, "errors:update") - m.handleMessage(requestMessage(1, "errors:clear", ClearParams{ID: id, ClearedBy: "node-self"})) + m.handleMessage(requestMessage(t, 1, "errors:clear", ClearParams{ID: id, ClearedBy: "node-self"})) resp := readResponseFrame(t, rw) require.Nil(t, resp.Error, "errors:clear returned error") @@ -316,14 +323,14 @@ func TestAcknowledge_SuppressesErrorUntilNextReport(t *testing.T) { m, rw := newTestManager(t) id := "ollama-proxy:upstream-unreachable:peer-x" - m.handleMessage(notificationMessage("errors:report", sampleError(id, 1000, "first"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 1000, "first"))) expectNotification(t, rw, "errors:update") - m.handleMessage(notificationMessage("errors:clear", ClearParams{ID: id})) + m.handleMessage(notificationMessage(t, "errors:clear", ClearParams{ID: id})) expectNotification(t, rw, "errors:update") // Re-emit must resurrect — newer timestamp. - m.handleMessage(notificationMessage("errors:report", sampleError(id, 2000, "still broken"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 2000, "still broken"))) update := expectNotification(t, rw, "errors:update") got := decodeResult[[]ServiceError](t, Message{Result: update.Params}) require.Len(t, got, 1, "after clear+reemit, update payload") @@ -345,7 +352,7 @@ func TestReport_RejectsMissingRequiredFields(t *testing.T) { ServiceError{ID: "x:y", Timestamp: 1}, -32602) // Notification form with no id: no response, no update push. - m.handleMessage(notificationMessage("errors:report", ServiceError{Message: "no id", Timestamp: 1})) + m.handleMessage(notificationMessage(t, "errors:report", ServiceError{Message: "no id", Timestamp: 1})) assertNoNotification(t, rw) assertNoResponse(t, rw) @@ -361,7 +368,7 @@ func TestClear_RejectsMissingID(t *testing.T) { callExpectError(t, m, rw, 1, "errors:clear", ClearParams{}, -32602) - m.handleMessage(notificationMessage("errors:clear", ClearParams{})) + m.handleMessage(notificationMessage(t, "errors:clear", ClearParams{})) assertNoNotification(t, rw) assertNoResponse(t, rw) } @@ -374,9 +381,9 @@ func TestUpdatePayload_IsFullSortedList(t *testing.T) { m, rw := newTestManager(t) // Emit in non-sorted order. - m.handleMessage(notificationMessage("errors:report", sampleError("z:later", 1000, "z"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError("z:later", 1000, "z"))) expectNotification(t, rw, "errors:update") - m.handleMessage(notificationMessage("errors:report", sampleError("a:earlier", 2000, "a"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError("a:earlier", 2000, "a"))) update := expectNotification(t, rw, "errors:update") got := decodeResult[[]ServiceError](t, Message{Result: update.Params}) @@ -400,10 +407,10 @@ func TestClear_IgnoresCallerSuppliedClearedBy(t *testing.T) { m, rw := newTestManager(t) id := "manual-nodes:probe-failed:peer-q" - m.handleMessage(notificationMessage("errors:report", sampleError(id, 1000, "down"))) + m.handleMessage(notificationMessage(t, "errors:report", sampleError(id, 1000, "down"))) expectNotification(t, rw, "errors:update") - m.handleMessage(notificationMessage("errors:clear", ClearParams{ID: id, ClearedBy: "node-other"})) + m.handleMessage(notificationMessage(t, "errors:clear", ClearParams{ID: id, ClearedBy: "node-other"})) expectNotification(t, rw, "errors:update") require.Empty(t, callAndDecode[[]ServiceError](t, m, rw, 1, "errors:get-initial", nil), "after foreign-clearedBy clear, list") @@ -421,7 +428,7 @@ func TestReport_PreservesNodeIdentity(t *testing.T) { Timestamp: 1000, NodeID: "peer-node-id-123", } - m.handleMessage(notificationMessage("errors:report", e)) + m.handleMessage(notificationMessage(t, "errors:report", e)) expectNotification(t, rw, "errors:update") list := callAndDecode[[]ServiceError](t, m, rw, 1, "errors:get-initial", nil) diff --git a/services/nvpair-errors/peersync_test.go b/services/nvpair-errors/peersync_test.go index 021b0ddf..e1cf4024 100644 --- a/services/nvpair-errors/peersync_test.go +++ b/services/nvpair-errors/peersync_test.go @@ -139,7 +139,8 @@ func TestHTTPIngest_ReconcilesPeerSnapshot(t *testing.T) { NodeID: "node-b", Errors: []ServiceError{localErr("b:one", "node-b", 1000)}, } - body, _ := json.Marshal(env) + body, err := json.Marshal(env) + require.NoError(t, err, "marshal sync envelope") resp, err := client.Post(srv.URL+"/v1/errors", "application/json", bytes.NewReader(body)) require.NoError(t, err, "POST") defer resp.Body.Close() @@ -157,7 +158,8 @@ func TestHTTPIngest_RejectsMissingNodeID(t *testing.T) { m := managerForNode("node-a") srv, client := servePinnedErrorsMux(t, m) - body, _ := json.Marshal(errors.SyncEnvelope{Errors: []ServiceError{localErr("x", "node-b", 1)}}) + body, err := json.Marshal(errors.SyncEnvelope{Errors: []ServiceError{localErr("x", "node-b", 1)}}) + require.NoError(t, err, "marshal sync envelope without node ID") resp, err := client.Post(srv.URL+"/v1/errors", "application/json", bytes.NewReader(body)) require.NoError(t, err, "POST") defer resp.Body.Close() diff --git a/services/nvpair-job-scheduler/delivery_test.go b/services/nvpair-job-scheduler/delivery_test.go index 86c9f3d5..33346b40 100644 --- a/services/nvpair-job-scheduler/delivery_test.go +++ b/services/nvpair-job-scheduler/delivery_test.go @@ -54,7 +54,7 @@ func TestFailedNotification_RetriesWithoutChangingRanks(t *testing.T) { m.recomputeAll(false) m.recomputeAll(false) for _, engine := range schedulerEngines { - orders := writer.orders(engine) + orders := writer.orders(t, engine) require.Len(t, orders, 1, " (%v)", engine) assertStrs(t, orders[0], []string{"test-node-a", "test-node-b"}) assert.NotEqual(t, int64(0), m.status().Engines[engine].LastEmittedAt, "successful delivery was not recorded") @@ -78,7 +78,7 @@ func TestFailedChangedNotification_RetainsDeliveredRanks(t *testing.T) { assert.Equal(t, previous.Emitted, current.Emitted, "failed update replaced the last successfully delivered snapshot") assert.Equal(t, previous.LastEmittedAt, current.LastEmittedAt, "failed update replaced the last successfully delivered snapshot") m.recomputeAll(false) - orders := writer.orders(engine) + orders := writer.orders(t, engine) require.Len(t, orders, 2, "received") assertStrs(t, orders[1], []string{"test-node-a", "test-node-b", "test-node-c"}) } @@ -93,5 +93,5 @@ func TestFailedForcedNotification_RetainsDeliveredTimestamp(t *testing.T) { m.recomputeAll(true) assert.Equal(t, int64(1), m.status().Engines[engine].LastEmittedAt, "failed forced delivery advanced timestamp") m.recomputeAll(false) - assert.Len(t, writer.orders(engine), 1, "previously delivered unchanged ranks were emitted") + assert.Len(t, writer.orders(t, engine), 1, "previously delivered unchanged ranks were emitted") } diff --git a/services/nvpair-job-scheduler/schedule_test.go b/services/nvpair-job-scheduler/schedule_test.go index 2532e1c2..e8405987 100644 --- a/services/nvpair-job-scheduler/schedule_test.go +++ b/services/nvpair-job-scheduler/schedule_test.go @@ -46,7 +46,8 @@ func (r *capRW) String() string { } // priorities returns every schedule:priority snapshot emitted for an engine. -func (r *capRW) priorities(engine string) []schedulePriorityParams { +func (r *capRW) priorities(t *testing.T, engine string) []schedulePriorityParams { + t.Helper() r.mu.Lock() defer r.mu.Unlock() var out []schedulePriorityParams @@ -56,11 +57,13 @@ func (r *capRW) priorities(engine string) []schedulePriorityParams { continue } var msg Message - if json.Unmarshal([]byte(line), &msg) != nil || msg.Method != "schedule:priority" { + require.NoError(t, json.Unmarshal([]byte(line), &msg), "decode scheduler frame") + if msg.Method != "schedule:priority" { continue } var p schedulePriorityParams - if json.Unmarshal(msg.Params, &p) != nil || p.Engine != engine { + require.NoError(t, json.Unmarshal(msg.Params, &p), "decode scheduler priority") + if p.Engine != engine { continue } out = append(out, p) @@ -69,8 +72,9 @@ func (r *capRW) priorities(engine string) []schedulePriorityParams { } // orders returns every schedule:priority node-order emitted for an engine. -func (r *capRW) orders(engine string) [][]string { - priorities := r.priorities(engine) +func (r *capRW) orders(t *testing.T, engine string) [][]string { + t.Helper() + priorities := r.priorities(t, engine) out := make([][]string, 0, len(priorities)) for _, p := range priorities { out = append(out, p.Nodes) @@ -281,7 +285,7 @@ func TestRank_BalancesMixedEngineWorkAcrossNodes(t *testing.T) { m.recomputeAll(false) for _, engine := range schedulerEngines { - got := rec.orders(engine) + got := rec.orders(t, engine) require.Len(t, got, 1, " (%v)", engine) assertStrs(t, got[0], []string{"c", "b", "a"}) } @@ -403,7 +407,7 @@ func TestHandleMessage_RebalancesImmediatelyAcrossEngines(t *testing.T) { m.handleMessage(&Message{JSONRPC: "2.0", Method: "workloads:upsert", Params: upsertJSON(t, job)}) for _, engine := range schedulerEngines { - got := rec.orders(engine) + got := rec.orders(t, engine) require.Len(t, got, 2, " (%v)", engine) assertStrs(t, got[0], []string{"a", "b"}) assertStrs(t, got[1], []string{"b", "a"}) @@ -412,7 +416,7 @@ func TestHandleMessage_RebalancesImmediatelyAcrossEngines(t *testing.T) { job.State = "completed" m.handleMessage(&Message{JSONRPC: "2.0", Method: "workloads:upsert", Params: upsertJSON(t, job)}) for _, engine := range schedulerEngines { - got := rec.orders(engine) + got := rec.orders(t, engine) require.Len(t, got, 3, " (%v)", engine) assertStrs(t, got[2], []string{"a", "b"}) } @@ -433,7 +437,7 @@ func TestHandleMessage_RebalancesOnFailoverRepoint(t *testing.T) { m.handleMessage(&Message{JSONRPC: "2.0", Method: "workloads:upsert", Params: upsertJSON(t, job)}) for _, engine := range schedulerEngines { - got := rec.orders(engine) + got := rec.orders(t, engine) require.Len(t, got, 3, " (%v)", engine) assertStrs(t, got[1], []string{"b", "c", "a"}) assertStrs(t, got[2], []string{"a", "b", "c"}) @@ -471,7 +475,7 @@ func TestRecomputeAll_SerializesOlderAndNewerResults(t *testing.T) { } } for _, engine := range schedulerEngines { - got := rw.recorder.orders(engine) + got := rw.recorder.orders(t, engine) require.Len(t, got, 2, " (%v)", engine) assertStrs(t, got[0], []string{"a", "b"}) assertStrs(t, got[1], []string{"b", "a"}) @@ -517,11 +521,11 @@ func TestEmit_SendsSnapshotOnlyWhenRankingChanges(t *testing.T) { m := mgrWith(rec, []string{"a", "b"}) m.recomputeAll(false) - require.Len(t, rec.orders("ollama"), 1, "first compute should emit once") + require.Len(t, rec.orders(t, "ollama"), 1, "first compute should emit once") m.recomputeAll(false) // unchanged - require.Len(t, rec.orders("ollama"), 1, "unchanged order must not re-emit") + require.Len(t, rec.orders(t, "ollama"), 1, "unchanged order must not re-emit") m.recomputeAll(true) // forced - require.Len(t, rec.orders("ollama"), 2, "forced tick must re-emit") + require.Len(t, rec.orders(t, "ollama"), 2, "forced tick must re-emit") } func TestEmit_IncludesPendingRanks(t *testing.T) { @@ -531,7 +535,7 @@ func TestEmit_IncludesPendingRanks(t *testing.T) { ) m.recomputeAll(false) - got := rec.priorities("ollama") + got := rec.priorities(t, "ollama") require.Len(t, got, 1, "priority snapshots") want := []NodeRank{ {ID: "a", Pending: 0, GPUPressure: unknownGPUPressure, Rank: 0}, @@ -553,7 +557,7 @@ func TestEmit_RefreshesSnapshotWhenOnlyPendingCountsChange(t *testing.T) { m.recomputeAll(false) m.recomputeAll(false) // identical snapshot stays quiet - got := rec.priorities("ollama") + got := rec.priorities(t, "ollama") require.Len(t, got, 2, "priority snapshots") assertStrs(t, got[0].Nodes, []string{"a", "b"}) assertStrs(t, got[1].Nodes, []string{"a", "b"}) @@ -566,7 +570,7 @@ func TestEmit_SendsNothingWithoutNodes(t *testing.T) { rec := &capRW{} m := mgrWith(rec, nil) m.recomputeAll(false) - require.Empty(t, rec.orders("ollama"), "empty universe should stay silent") + require.Empty(t, rec.orders(t, "ollama"), "empty universe should stay silent") } // TestSetInterval_Floor: a sub-floor interval is clamped to 200ms. diff --git a/services/nvpair-job-scheduler/telemetry_test.go b/services/nvpair-job-scheduler/telemetry_test.go index 9fbb4045..c332e615 100644 --- a/services/nvpair-job-scheduler/telemetry_test.go +++ b/services/nvpair-job-scheduler/telemetry_test.go @@ -188,7 +188,7 @@ func TestTelemetryNotification_EmitsOnlyOnPressureChange(t *testing.T) { send("b", 20) // EWMA changes within pressure band 0 for _, engine := range schedulerEngines { - got := recorder.priorities(engine) + got := recorder.priorities(t, engine) require.Len(t, got, 2, " (%v)", engine) assertStrs(t, got[1].Nodes, []string{"b", "a"}) assert.Equal(t, 0, pressureOf(got[1].Ranks, "b"), " (%v)", engine) diff --git a/services/nvpair-manual-nodes/manager_test.go b/services/nvpair-manual-nodes/manager_test.go index 87de4180..30c20b56 100644 --- a/services/nvpair-manual-nodes/manager_test.go +++ b/services/nvpair-manual-nodes/manager_test.go @@ -86,7 +86,8 @@ func newTestManager() (*Manager, *captureRW, *fakeRoundTripper) { return m, rw, rt } -func configureHealthyNode(rt *fakeRoundTripper, addr string, models []string, info NodeInfoResponse) { +func configureHealthyNode(t *testing.T, rt *fakeRoundTripper, addr string, models []string, info NodeInfoResponse) { + t.Helper() host := net.JoinHostPort(addr, "11434") rt.set(http.MethodGet, host, "/", func(*http.Request) (*http.Response, error) { return httpJSON(http.StatusOK, `{}`) @@ -102,13 +103,15 @@ func configureHealthyNode(rt *fakeRoundTripper, addr string, models []string, in Name string `json:"name"` }{Name: name}) } - data, _ := json.Marshal(payload) + data, err := json.Marshal(payload) + assert.NoError(t, err, "marshal model inventory") return httpJSON(http.StatusOK, string(data)) }) infoHost := net.JoinHostPort(addr, "14318") rt.set(http.MethodGet, infoHost, "/v1/node-info", func(*http.Request) (*http.Response, error) { - data, _ := json.Marshal(info) + data, err := json.Marshal(info) + assert.NoError(t, err, "marshal node info") return httpJSON(http.StatusOK, string(data)) }) } @@ -116,7 +119,8 @@ func configureHealthyNode(rt *fakeRoundTripper, addr string, models []string, in // configureHealthyLMStudio registers a 200 GET /v1/models on addr:1234 // returning the given model ids in the OpenAI list shape, so probeLMStudio // reports the node up with those models. -func configureHealthyLMStudio(rt *fakeRoundTripper, addr string, models []string) { +func configureHealthyLMStudio(t *testing.T, rt *fakeRoundTripper, addr string, models []string) { + t.Helper() host := net.JoinHostPort(addr, "1234") rt.set(http.MethodGet, host, "/v1/models", func(*http.Request) (*http.Response, error) { var payload struct { @@ -131,7 +135,8 @@ func configureHealthyLMStudio(rt *fakeRoundTripper, addr string, models []string ID string `json:"id"` }{ID: id}) } - data, _ := json.Marshal(payload) + data, err := json.Marshal(payload) + assert.NoError(t, err, "marshal LM Studio model inventory") return httpJSON(http.StatusOK, string(data)) }) } @@ -141,7 +146,7 @@ func configureHealthyLMStudio(rt *fakeRoundTripper, addr string, models []string // one (no handler registered, so the request errors) reports down. func TestProbeLMStudio_ReportsModels(t *testing.T) { m, _, rt := newTestManager() - configureHealthyLMStudio(rt, "node.local", []string{"qwen2.5-7b", "llama-3.1-8b"}) + configureHealthyLMStudio(t, rt, "node.local", []string{"qwen2.5-7b", "llama-3.1-8b"}) up, models := m.probeLMStudio("node.local", lmStudioPort) require.True(t, up, "expected lmstudio up") @@ -152,21 +157,28 @@ func TestProbeLMStudio_ReportsModels(t *testing.T) { assert.Nil(t, downModels, "expected absent lmstudio down, got up (%v, %v)", downUp, downModels) } -func requestMessage(id int, method string, params any) *Message { - idData, _ := json.Marshal(id) +func requestMessage(t *testing.T, id int, method string, params any) *Message { + t.Helper() + idData, err := json.Marshal(id) + require.NoError(t, err, "marshal request ID") idRaw := json.RawMessage(idData) - paramsRaw, _ := json.Marshal(params) + paramsRaw, err := json.Marshal(params) + require.NoError(t, err, "marshal request params") return &Message{JSONRPC: "2.0", ID: &idRaw, Method: method, Params: paramsRaw} } -func requestMessageRaw(id int, method string, params json.RawMessage) *Message { - idData, _ := json.Marshal(id) +func requestMessageRaw(t *testing.T, id int, method string, params json.RawMessage) *Message { + t.Helper() + idData, err := json.Marshal(id) + require.NoError(t, err, "marshal request ID") idRaw := json.RawMessage(idData) return &Message{JSONRPC: "2.0", ID: &idRaw, Method: method, Params: params} } -func notificationMessage(method string, params any) *Message { - paramsRaw, _ := json.Marshal(params) +func notificationMessage(t *testing.T, method string, params any) *Message { + t.Helper() + paramsRaw, err := json.Marshal(params) + require.NoError(t, err, "marshal notification params") return &Message{JSONRPC: "2.0", Method: method, Params: paramsRaw} } @@ -232,13 +244,15 @@ func decodeParams[T any](t *testing.T, msg Message) T { return result } -func responseWithID(id int) func(Message) bool { +func responseWithID(t *testing.T, id int) func(Message) bool { + t.Helper() return func(msg Message) bool { if msg.ID == nil { return false } var got int - return json.Unmarshal(*msg.ID, &got) == nil && got == id + require.NoError(t, json.Unmarshal(*msg.ID, &got), "decode response ID") + return got == id } } @@ -268,11 +282,11 @@ func TestNodeID_PrefersConfiguredNameOverAddressFallback(t *testing.T) { func TestNodeAdd_RespondsWithInitialStatusThenDiscoversProbeResult(t *testing.T) { m, rw, rt := newTestManager() - configureHealthyNode(rt, "node.local", []string{"llama3", "mistral"}, sampleInfo()) + configureHealthyNode(t, rt, "node.local", []string{"llama3", "mistral"}, sampleInfo()) - m.handleMessage(requestMessage(1, "node/add", ManualEntry{Address: "node.local", Name: "lab"})) + m.handleMessage(requestMessage(t, 1, "node/add", ManualEntry{Address: "node.local", Name: "lab"})) - resp := readCaptureUntil(t, rw, responseWithID(1)) + resp := readCaptureUntil(t, rw, responseWithID(t, 1)) initial := decodeResult[ManualNodeStatus](t, resp) assert.Equal(t, "lab", initial.ID, "initial status (%v)", initial) assert.Equal(t, "node.local", initial.Address, "initial status (%v)", initial) @@ -307,21 +321,21 @@ func TestNodeAdd_RejectsInvalidParameters(t *testing.T) { assert.Equal(t, -32602, resp.Error.Code) }) } - test("malformed params", requestMessageRaw(1, "node/add", json.RawMessage(`"bad"`))) - test("missing address", requestMessage(2, "node/add", ManualEntry{})) + test("malformed params", requestMessageRaw(t, 1, "node/add", json.RawMessage(`"bad"`))) + test("missing address", requestMessage(t, 2, "node/add", ManualEntry{})) require.Empty(t, m.listNodes(), "validation errors added nodes") } func TestNodesList_ReturnsCurrentStatuses(t *testing.T) { m, rw, rt := newTestManager() - configureHealthyNode(rt, "node.local", []string{"llama3"}, sampleInfo()) + configureHealthyNode(t, rt, "node.local", []string{"llama3"}, sampleInfo()) - m.handleMessage(requestMessage(1, "node/add", ManualEntry{Address: "node.local"})) + m.handleMessage(requestMessage(t, 1, "node/add", ManualEntry{Address: "node.local"})) _ = readCaptureUntil(t, rw, methodIs("node/discovered")) - m.handleMessage(requestMessage(2, "nodes/list", nil)) - resp := readCaptureUntil(t, rw, responseWithID(2)) + m.handleMessage(requestMessage(t, 2, "nodes/list", nil)) + resp := readCaptureUntil(t, rw, responseWithID(t, 2)) var result struct { Nodes []ManualNodeStatus `json:"nodes"` } @@ -338,18 +352,18 @@ func TestNodeRemove_ReturnsRemovedAndNotifies(t *testing.T) { status: ManualNodeStatus{ID: "lab", Address: "node.local", OllamaPort: 11434, NodeInfoPort: 14318}, } - m.handleMessage(requestMessage(1, "node/remove", map[string]string{"id": "lab"})) + m.handleMessage(requestMessage(t, 1, "node/remove", map[string]string{"id": "lab"})) removed := readCaptureFrame(t, rw) assert.Equal(t, "node/removed", removed.Method, "first remove frame (%v)", removed) status := decodeParams[ManualNodeStatus](t, removed) assert.Equal(t, "lab", status.ID, "removed params (%v)", status) - resp := readCaptureUntil(t, rw, responseWithID(1)) + resp := readCaptureUntil(t, rw, responseWithID(t, 1)) result := decodeResult[map[string]bool](t, resp) assert.True(t, result["removed"], "removed result (%v)", result) require.Empty(t, m.listNodes(), "node still listed after removal") - m.handleMessage(requestMessage(2, "node/remove", map[string]string{"id": "lab"})) - resp = readCaptureUntil(t, rw, responseWithID(2)) + m.handleMessage(requestMessage(t, 2, "node/remove", map[string]string{"id": "lab"})) + resp = readCaptureUntil(t, rw, responseWithID(t, 2)) result = decodeResult[map[string]bool](t, resp) assert.False(t, result["removed"], "second removal result (%v)", result) assertNoCaptureMethod(t, rw, "node/removed") @@ -392,12 +406,12 @@ func TestProbeNode_EmitsUpdatedOnStateChange(t *testing.T) { m, rw, rt := newTestManager() m.nodes["lab"] = &trackedNode{entry: ManualEntry{Name: "lab", Address: "node.local"}, status: ManualNodeStatus{ID: "lab", Address: "node.local", OllamaPort: 11434, NodeInfoPort: 14318}} - configureHealthyNode(rt, "node.local", []string{"llama3"}, sampleInfo()) + configureHealthyNode(t, rt, "node.local", []string{"llama3"}, sampleInfo()) m.probeNode(ManualEntry{Name: "lab", Address: "node.local"}) first := decodeParams[ManualNodeStatus](t, readCaptureUntil(t, rw, methodIs("node/updated"))) assert.Equal(t, []string{"llama3"}, first.OllamaModels, "first update models") - configureHealthyNode(rt, "node.local", []string{"mistral"}, sampleInfo()) + configureHealthyNode(t, rt, "node.local", []string{"mistral"}, sampleInfo()) m.probeNode(ManualEntry{Name: "lab", Address: "node.local"}) second := decodeParams[ManualNodeStatus](t, readCaptureUntil(t, rw, methodIs("node/updated"))) assert.Equal(t, []string{"mistral"}, second.OllamaModels, "second update models") @@ -407,7 +421,7 @@ func TestProbeNode_EmitsNothingWhenStateIsUnchanged(t *testing.T) { m, rw, rt := newTestManager() entry := ManualEntry{Name: "lab", Address: "node.local"} m.nodes["lab"] = &trackedNode{entry: entry, status: ManualNodeStatus{ID: "lab", Address: "node.local", OllamaPort: 11434, NodeInfoPort: 14318}} - configureHealthyNode(rt, "node.local", []string{"llama3"}, sampleInfo()) + configureHealthyNode(t, rt, "node.local", []string{"llama3"}, sampleInfo()) m.probeNode(entry) _ = readCaptureUntil(t, rw, methodIs("node/updated")) @@ -461,7 +475,7 @@ func TestProbeFailure_PreservesHostUUID(t *testing.T) { info := sampleInfo() info.HostUUID = "node-uuid" - configureHealthyNode(rt, "node.local", []string{"llama3"}, info) + configureHealthyNode(t, rt, "node.local", []string{"llama3"}, info) // First probe learns the UUID. m.probeNode(entry) @@ -478,7 +492,7 @@ func TestProbeFailure_PreservesHostUUID(t *testing.T) { assert.Equal(t, "node-uuid", down.HostUUID, "HostUUID dropped on node-info failure") // node-info recovers: still the same UUID (no flap). - configureHealthyNode(rt, "node.local", []string{"llama3"}, info) + configureHealthyNode(t, rt, "node.local", []string{"llama3"}, info) m.probeNode(entry) up := decodeParams[ManualNodeStatus](t, readCaptureUntil(t, rw, methodIs("node/updated"))) assert.Equal(t, "node-uuid", up.HostUUID, "HostUUID after recovery") @@ -497,7 +511,7 @@ func TestTelemetryEquality_DistinguishesMissingCPUAndMemoryReadings(t *testing.T func TestUnknownMethod_ReturnsMethodNotFound(t *testing.T) { m, rw, _ := newTestManager() - m.handleMessage(requestMessage(1, "bogus", nil)) + m.handleMessage(requestMessage(t, 1, "bogus", nil)) resp := readCaptureFrame(t, rw) require.NotNil(t, resp.Error, "unknown method error") assert.Equal(t, -32601, resp.Error.Code, "unknown method error") @@ -505,12 +519,12 @@ func TestUnknownMethod_ReturnsMethodNotFound(t *testing.T) { func TestLogSetLevel_AppliesRequestedVerbosity(t *testing.T) { m, rw, _ := newTestManager() - m.handleMessage(requestMessage(1, applog.SetLevelMethod, applog.SetLevelParams{Level: "debug"})) + m.handleMessage(requestMessage(t, 1, applog.SetLevelMethod, applog.SetLevelParams{Level: "debug"})) resp := readCaptureFrame(t, rw) result := decodeResult[map[string]string](t, resp) assert.Equal(t, "debug", result["level"], "log/set-level result (%v)", result) - m.handleMessage(requestMessage(2, applog.SetLevelMethod, applog.SetLevelParams{Level: "not-a-level"})) + m.handleMessage(requestMessage(t, 2, applog.SetLevelMethod, applog.SetLevelParams{Level: "not-a-level"})) resp = readCaptureFrame(t, rw) require.NotNil(t, resp.Error, "invalid log/set-level error") assert.Equal(t, -32602, resp.Error.Code, "invalid log/set-level error") @@ -536,7 +550,7 @@ func TestShutdownRequest_CancelsRun(t *testing.T) { writePipeRequest(t, client, 7, "shutdown", nil) resp := readPipeFrame(t, client, reader) - assert.True(t, responseWithID(7)(resp), "shutdown response (%v)", resp) + assert.True(t, responseWithID(t, 7)(resp), "shutdown response (%v)", resp) require.Nil(t, resp.Error, "shutdown response (%v)", resp) select { @@ -549,7 +563,7 @@ func TestShutdownRequest_CancelsRun(t *testing.T) { func TestNotification_IsIgnored(t *testing.T) { m, rw, _ := newTestManager() - m.handleMessage(notificationMessage("node/add", ManualEntry{Address: "node.local"})) + m.handleMessage(notificationMessage(t, "node/add", ManualEntry{Address: "node.local"})) require.Empty(t, m.listNodes(), "notification mutated state") assertNoCaptureMethod(t, rw, "") } diff --git a/services/nvpair-node-info/cluster_mtls_test.go b/services/nvpair-node-info/cluster_mtls_test.go index a5775630..385da647 100644 --- a/services/nvpair-node-info/cluster_mtls_test.go +++ b/services/nvpair-node-info/cluster_mtls_test.go @@ -31,8 +31,10 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "genkey") - serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - uri, _ := url.Parse("urn:nvpair:node:" + uuid) + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + require.NoError(t, err) + uri, err := url.Parse("urn:nvpair:node:" + uuid) + require.NoError(t, err) tmpl := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: uuid}, @@ -45,7 +47,8 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) require.NoError(t, err, "create cert") - keyDER, _ := x509.MarshalPKCS8PrivateKey(priv) + keyDER, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err) certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) return certPEM, keyPEM @@ -61,7 +64,8 @@ func setupNode(t *testing.T, certPEM, keyPEM []byte, pins map[string][]byte) str td := filepath.Join(dir, "trusted") require.NoError(t, os.MkdirAll(td, 0o700)) for uuid, pcert := range pins { - body, _ := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(pcert)}) + body, err := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(pcert)}) + require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(td, uuid+".json"), body, 0o600)) } return dir @@ -103,8 +107,9 @@ func TestNodeInfoHandler_RequiresConfiguredMutualTLS(t *testing.T) { client := &http.Client{Timeout: 5 * time.Second, Transport: &http.Transport{TLSClientConfig: cfg}} resp, err := client.Get(srv.URL + "/v1/node-info") require.NoError(t, err, "get") - defer resp.Body.Close() - b, _ := io.ReadAll(resp.Body) + defer func() { require.NoError(t, resp.Body.Close()) }() + b, err := io.ReadAll(resp.Body) + require.NoError(t, err) return resp.StatusCode, string(b) } diff --git a/services/nvpair-node-info/observed_wiring_test.go b/services/nvpair-node-info/observed_wiring_test.go index b32b2453..a9c9d0bc 100644 --- a/services/nvpair-node-info/observed_wiring_test.go +++ b/services/nvpair-node-info/observed_wiring_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/applog" @@ -54,7 +55,8 @@ func TestServedRequest_ReportsTheAddressTheClientReached(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/v1/node-info", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"GPUs":[],"telemetryValid":false,"msSince":0}`)) + _, err := w.Write([]byte(`{"GPUs":[],"telemetryValid":false,"msSince":0}`)) + assert.NoError(t, err) }) server := &http.Server{ Handler: mux, @@ -77,8 +79,9 @@ func TestServedRequest_ReportsTheAddressTheClientReached(t *testing.T) { if err != nil { t.Skipf("cannot reach this host at %s: %v", listener.Addr(), err) } - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() + _, err = io.Copy(io.Discard, resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) require.Equal(t, http.StatusOK, resp.StatusCode, "inventory status") var out bytes.Buffer @@ -138,7 +141,8 @@ func TestServer_RecordsTheServingConnectionWhenARequestArrives(t *testing.T) { observer := newAddressObserver() mux := http.NewServeMux() mux.HandleFunc("/v1/node-info", func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write([]byte(`{"GPUs":[],"telemetryValid":false,"msSince":0}`)) + _, err := w.Write([]byte(`{"GPUs":[],"telemetryValid":false,"msSince":0}`)) + assert.NoError(t, err) }) server := &http.Server{ Handler: mux, @@ -160,8 +164,9 @@ func TestServer_RecordsTheServingConnectionWhenARequestArrives(t *testing.T) { }} resp, err := client.Get("http://10.172.54.70:14318/v1/node-info") require.NoError(t, err, "inventory request") - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() + _, err = io.Copy(io.Discard, resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) require.Equal(t, []string{"10.172.54.70"}, observer.addresses(), "observed") } diff --git a/services/nvpair-node-scanner/cluster_identity_refresh_test.go b/services/nvpair-node-scanner/cluster_identity_refresh_test.go index 81cdd2a9..d29c6d13 100644 --- a/services/nvpair-node-scanner/cluster_identity_refresh_test.go +++ b/services/nvpair-node-scanner/cluster_identity_refresh_test.go @@ -40,7 +40,8 @@ func (s *nodeInfoStub) set(status int, body string) { s.mu.Unlock() } -func (s *nodeInfoStub) handler() http.HandlerFunc { +func (s *nodeInfoStub) handler(t *testing.T) http.HandlerFunc { + t.Helper() return func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/node-info" { http.NotFound(w, r) @@ -54,7 +55,8 @@ func (s *nodeInfoStub) handler() http.HandlerFunc { } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - _, _ = w.Write([]byte(body)) + _, err := w.Write([]byte(body)) + assert.NoError(t, err) } } @@ -62,7 +64,7 @@ func startNodeInfoStub(t *testing.T, body string) (*nodeInfoStub, int) { t.Helper() stub := &nodeInfoStub{} stub.set(http.StatusOK, body) - srv := httptest.NewServer(stub.handler()) + srv := httptest.NewServer(stub.handler(t)) t.Cleanup(srv.Close) return stub, portFromURL(t, srv.URL) } diff --git a/services/nvpair-node-scanner/connreuse_test.go b/services/nvpair-node-scanner/connreuse_test.go index 4a03418c..bd96b00a 100644 --- a/services/nvpair-node-scanner/connreuse_test.go +++ b/services/nvpair-node-scanner/connreuse_test.go @@ -14,6 +14,7 @@ import ( "sync" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/clustertrust" @@ -49,7 +50,7 @@ func TestModelsClient_ReusesPeerConnections(t *testing.T) { http.Error(w, "forbidden", http.StatusForbidden) return } - _ = json.NewEncoder(w).Encode(map[string]any{"models": []string{"m"}}) + assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"models": []string{"m"}})) }) ts := httptest.NewUnstartedServer(mux) ts.Config.ConnState = func(_ net.Conn, state http.ConnState) { diff --git a/services/nvpair-node-scanner/daemon_test.go b/services/nvpair-node-scanner/daemon_test.go index 9e4595e0..cfc2e822 100644 --- a/services/nvpair-node-scanner/daemon_test.go +++ b/services/nvpair-node-scanner/daemon_test.go @@ -78,7 +78,8 @@ func TestReloadIdentity_AdoptsClusterPrincipal(t *testing.T) { // cluster-manager writes its principal under /cluster/identity.json. idPath := filepath.Join(base, "cluster", "identity.json") require.NoError(t, os.MkdirAll(filepath.Dir(idPath), 0o700), "mkdir") - body, _ := json.Marshal(map[string]any{"node_uuid": "cluster-principal-Y", "created_at": 1}) + body, err := json.Marshal(map[string]any{"node_uuid": "cluster-principal-Y", "created_at": 1}) + require.NoError(t, err) require.NoError(t, os.WriteFile(idPath, body, 0o600), "write identity.json") d.reloadIdentity() @@ -137,12 +138,13 @@ func TestPublishSelf_EnrichesOverLoopback(t *testing.T) { w.WriteHeader(http.StatusNotFound) return } - _ = json.NewEncoder(w).Encode(want) + assert.NoError(t, json.NewEncoder(w).Encode(want)) })) defer srv.Close() _, portStr, err := net.SplitHostPort(srv.Listener.Addr().String()) require.NoError(t, err, "split stub addr") - port, _ := strconv.Atoi(portStr) + port, err := strconv.Atoi(portStr) + require.NoError(t, err) // The node advertises the LAN address (which no server answers on in the // test); enrichment must still succeed via loopback, where the stub listens. @@ -195,7 +197,8 @@ func TestReachable_RetainsNodeAcrossTransientFailures(t *testing.T) { http.NotFound(w, r) return } - _, _ = w.Write([]byte(`{}`)) + _, err := w.Write([]byte(`{}`)) + assert.NoError(t, err) })) defer niSrv.Close() niURL, err := url.Parse(niSrv.URL) @@ -216,7 +219,7 @@ func TestReachable_RetainsNodeAcrossTransientFailures(t *testing.T) { dl, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err, "dead listen") deadPort := dl.Addr().(*net.TCPAddr).Port - dl.Close() + require.NoError(t, dl.Close()) d := &daemon{ lastInfo: make(map[string]NodeInfoResponse), @@ -266,10 +269,10 @@ func TestReachable_RetainsNodeAcrossTransientFailures(t *testing.T) { func TestEnrich_FallsBackToASecondPublishedAddress(t *testing.T) { const hostUUID = "host-enrich-failover" niSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(NodeInfoResponse{ + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{ HostUUID: hostUUID, GPUs: []GPUInfo{{Name: "GPU from reachable address"}}, - }) + })) })) defer niSrv.Close() niURL, err := url.Parse(niSrv.URL) @@ -307,7 +310,7 @@ func TestReachableSelf_DialsLoopback(t *testing.T) { http.NotFound(w, r) return } - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: selfUUID}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: selfUUID})) })) defer srv.Close() _, portStr, err := net.SplitHostPort(srv.Listener.Addr().String()) diff --git a/services/nvpair-node-scanner/ghost_node_test.go b/services/nvpair-node-scanner/ghost_node_test.go index 8b3753cb..e702b76d 100644 --- a/services/nvpair-node-scanner/ghost_node_test.go +++ b/services/nvpair-node-scanner/ghost_node_test.go @@ -43,7 +43,7 @@ func nodeInfoServer(t *testing.T, hostUUID string) (host string, port int) { return } w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: hostUUID}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: hostUUID})) })) t.Cleanup(srv.Close) return splitHostPort(t, strings.TrimPrefix(srv.URL, "http://")) @@ -55,7 +55,7 @@ func openPort(t *testing.T) int { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err, "listen") - t.Cleanup(func() { _ = ln.Close() }) + t.Cleanup(func() { assert.NoError(t, ln.Close()) }) _, port := splitHostPort(t, ln.Addr().String()) return port } diff --git a/services/nvpair-node-scanner/node_info_outage_test.go b/services/nvpair-node-scanner/node_info_outage_test.go index d9469c83..75d7ae7f 100644 --- a/services/nvpair-node-scanner/node_info_outage_test.go +++ b/services/nvpair-node-scanner/node_info_outage_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -81,7 +82,7 @@ func TestNodeInfoConnect_GivesUpBeforeTheRequestBudget(t *testing.T) { start := time.Now() conn, err := nodeInfoTransport(nil).DialContext(context.Background(), "tcp", "192.0.2.1:14300") if err == nil { - _ = conn.Close() + require.NoError(t, conn.Close()) require.FailNow(t, "nothing may answer at a documentation address") } require.Less(t, time.Since(start), nodeInfoFetchTimeout, "connect must give up at the dial budget %v", nodeInfoDialTimeout) @@ -99,7 +100,7 @@ func TestNodeInfoFetches_UseIndependentRequestBudgets(t *testing.T) { server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { requests.Add(1) time.Sleep(responseDelay) - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"})) })) var connections atomic.Int32 server.Config.ConnState = func(_ net.Conn, state http.ConnState) { @@ -161,10 +162,11 @@ func TestNodeInfoErrorBody_IsDrainedForConnectionReuse(t *testing.T) { server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { if requests.Add(1) == 1 { w.WriteHeader(http.StatusServiceUnavailable) - _, _ = w.Write([]byte("temporarily unavailable")) + _, err := w.Write([]byte("temporarily unavailable")) + assert.NoError(t, err) return } - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"})) })) var connections atomic.Int32 server.Config.ConnState = func(_ net.Conn, state http.ConnState) { diff --git a/services/nvpair-node-scanner/refresh_test.go b/services/nvpair-node-scanner/refresh_test.go index 40339738..78518fd2 100644 --- a/services/nvpair-node-scanner/refresh_test.go +++ b/services/nvpair-node-scanner/refresh_test.go @@ -43,7 +43,8 @@ func (s *modelsStub) set(status int, body map[string]any) { s.mu.Unlock() } -func (s *modelsStub) handler() http.HandlerFunc { +func (s *modelsStub) handler(t *testing.T) http.HandlerFunc { + t.Helper() return func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/models" { http.NotFound(w, r) @@ -57,7 +58,7 @@ func (s *modelsStub) handler() http.HandlerFunc { } w.WriteHeader(status) if body != nil { - _ = json.NewEncoder(w).Encode(body) + assert.NoError(t, json.NewEncoder(w).Encode(body)) } } } @@ -68,7 +69,7 @@ func startModelsStub(t *testing.T, status int, body map[string]any) (*modelsStub t.Helper() stub := &modelsStub{} stub.set(status, body) - srv := httptest.NewServer(stub.handler()) + srv := httptest.NewServer(stub.handler(t)) t.Cleanup(srv.Close) return stub, portFromURL(t, srv.URL) } @@ -420,7 +421,7 @@ func TestRefreshOnce_PollsSlowPeersConcurrently(t *testing.T) { handler := func(w http.ResponseWriter, r *http.Request) { entered <- struct{}{} <-release // hold until both peers are concurrently in-flight - _ = json.NewEncoder(w).Encode(map[string]any{"models": []string{"m"}}) + assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{"models": []string{"m"}})) } s1 := httptest.NewServer(http.HandlerFunc(handler)) s2 := httptest.NewServer(http.HandlerFunc(handler)) diff --git a/services/nvpair-node-scanner/telemetry_test.go b/services/nvpair-node-scanner/telemetry_test.go index 31261ab6..016bafd5 100644 --- a/services/nvpair-node-scanner/telemetry_test.go +++ b/services/nvpair-node-scanner/telemetry_test.go @@ -33,7 +33,7 @@ func TestRefreshNodeTelemetry_EmitsFreshnessAndUtilization(t *testing.T) { HostUUID: "node-a", } server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(response) + assert.NoError(t, json.NewEncoder(w).Encode(response)) })) defer server.Close() serverURL, err := url.Parse(server.URL) @@ -59,10 +59,10 @@ func TestRefreshNodeTelemetry_EmitsFreshnessAndUtilization(t *testing.T) { func TestRefreshNodeTelemetry_RejectsMismatchedIdentity(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(NodeInfoResponse{ + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{ HostUUID: "node-b", TelemetryValid: true, - }) + })) })) defer server.Close() serverURL, err := url.Parse(server.URL) @@ -84,11 +84,11 @@ func TestRefreshNodeTelemetry_RejectsMismatchedIdentity(t *testing.T) { // published list. func TestRefreshTelemetry_FailsOverToAnAnsweringAddress(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _ = json.NewEncoder(w).Encode(NodeInfoResponse{ + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{ HostUUID: "node-a", TelemetryValid: true, GPUs: []GPUInfo{{Name: "GPU 0", UtilizationPercent: 61}}, - }) + })) })) defer server.Close() serverURL, err := url.Parse(server.URL) @@ -219,7 +219,7 @@ func TestRefreshTelemetry_SkipsBackedOffPeerWithoutDelayingHealthyPeer(t *testin var backedOffCalls atomic.Int32 backedOff := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { backedOffCalls.Add(1) - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-backed-off"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-backed-off"})) })) defer backedOff.Close() backedOffURL, err := url.Parse(backedOff.URL) @@ -230,7 +230,7 @@ func TestRefreshTelemetry_SkipsBackedOffPeerWithoutDelayingHealthyPeer(t *testin var healthyCalls atomic.Int32 healthy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { healthyCalls.Add(1) - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-healthy"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-healthy"})) })) defer healthy.Close() healthyURL, err := url.Parse(healthy.URL) @@ -274,7 +274,7 @@ func TestBrowseTXTUpdate_PreservesTelemetryRetry(t *testing.T) { var calls atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { calls.Add(1) - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"})) })) defer server.Close() serverURL, err := url.Parse(server.URL) @@ -350,7 +350,7 @@ func TestBrowseEndpointUpdate_WaitsForOldTelemetryBeforeClaimingReplacement(t *t newServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { newEntered <- struct{}{} <-releaseNew - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "peer-uuid"})) })) oldEntered := make(chan struct{}, 1) releaseOld := make(chan struct{}, 1) @@ -459,7 +459,7 @@ func TestTelemetryLoop_KeepsHealthyNodeOnCadenceWhilePeerIsBlocked(t *testing.T) default: } } - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-healthy"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-healthy"})) })) defer healthy.Close() healthyURL, err := url.Parse(healthy.URL) @@ -528,7 +528,7 @@ func TestTelemetryLoop_DoesNotOverlapNodePolls(t *testing.T) { } select { case <-time.After(20 * time.Millisecond): - _ = json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-a"}) + assert.NoError(t, json.NewEncoder(w).Encode(NodeInfoResponse{HostUUID: "node-a"})) case <-r.Context().Done(): } })) diff --git a/services/nvpair-node-scanner/tls_test.go b/services/nvpair-node-scanner/tls_test.go index 9be1269b..7da11a1d 100644 --- a/services/nvpair-node-scanner/tls_test.go +++ b/services/nvpair-node-scanner/tls_test.go @@ -72,13 +72,14 @@ func TestFetchNodeInfo_SelectsClientForConfiguredTransport(t *testing.T) { w.WriteHeader(http.StatusNotFound) return } - _ = json.NewEncoder(w).Encode(want) + assert.NoError(t, json.NewEncoder(w).Encode(want)) })) defer srv.Close() host, portStr, err := net.SplitHostPort(srv.Listener.Addr().String()) require.NoError(t, err) - port, _ := strconv.Atoi(portStr) + port, err := strconv.Atoi(portStr) + require.NoError(t, err) // With the TLS client (which trusts the test server), the HTTPS fetch works. dTLS := &daemon{http: &http.Client{Timeout: nodeInfoFetchTimeout}, tlsHTTP: srv.Client()} diff --git a/services/nvpair-node-settings/manager_test.go b/services/nvpair-node-settings/manager_test.go index 090d34df..5d5761c1 100644 --- a/services/nvpair-node-settings/manager_test.go +++ b/services/nvpair-node-settings/manager_test.go @@ -81,21 +81,28 @@ func newTestManager(t *testing.T) (*Manager, *captureRW, string) { return m, rw, path } -func requestMessage(id int, method string, params any) *Message { - idData, _ := json.Marshal(id) +func requestMessage(t *testing.T, id int, method string, params any) *Message { + t.Helper() + idData, err := json.Marshal(id) + require.NoError(t, err) idRaw := json.RawMessage(idData) - paramsRaw, _ := json.Marshal(params) + paramsRaw, err := json.Marshal(params) + require.NoError(t, err) return &Message{JSONRPC: "2.0", ID: &idRaw, Method: method, Params: paramsRaw} } -func requestMessageRaw(id int, method string, params json.RawMessage) *Message { - idData, _ := json.Marshal(id) +func requestMessageRaw(t *testing.T, id int, method string, params json.RawMessage) *Message { + t.Helper() + idData, err := json.Marshal(id) + require.NoError(t, err) idRaw := json.RawMessage(idData) return &Message{JSONRPC: "2.0", ID: &idRaw, Method: method, Params: params} } -func notificationMessage(method string, params any) *Message { - paramsRaw, _ := json.Marshal(params) +func notificationMessage(t *testing.T, method string, params any) *Message { + t.Helper() + paramsRaw, err := json.Marshal(params) + require.NoError(t, err) return &Message{JSONRPC: "2.0", Method: method, Params: paramsRaw} } @@ -167,13 +174,16 @@ func decodeResult[T any](t *testing.T, msg Message) T { return result } -func responseWithID(id int) func(Message) bool { +func responseWithID(t *testing.T, id int) func(Message) bool { + t.Helper() return func(msg Message) bool { + t.Helper() if msg.ID == nil { return false } var got int - return json.Unmarshal(*msg.ID, &got) == nil && got == id + require.NoError(t, json.Unmarshal(*msg.ID, &got)) + return got == id } } @@ -183,17 +193,17 @@ func responseWithID(id int) func(Message) bool { // Anything that produces an RPC error fails the test via decodeResult. func callAndDecode[T any](t *testing.T, m *Manager, rw *captureRW, id int, method string, params any) T { t.Helper() - m.handleMessage(requestMessage(id, method, params)) + m.handleMessage(requestMessage(t, id, method, params)) resp := readCaptureFrame(t, rw) - require.True(t, responseWithID(id)(resp), "response id mismatch: (%v, %v)", resp, id) + require.True(t, responseWithID(t, id)(resp), "response id mismatch: (%v, %v)", resp, id) return decodeResult[T](t, resp) } func callExpectError(t *testing.T, m *Manager, rw *captureRW, id int, method string, params any, wantCode int) *RPCError { t.Helper() - m.handleMessage(requestMessage(id, method, params)) + m.handleMessage(requestMessage(t, id, method, params)) resp := readCaptureFrame(t, rw) - require.True(t, responseWithID(id)(resp), "response id mismatch: (%v, %v)", resp, id) + require.True(t, responseWithID(t, id)(resp), "response id mismatch: (%v, %v)", resp, id) require.NotNil(t, resp.Error, "expected error (%v)", wantCode) require.Equal(t, wantCode, resp.Error.Code, "error code") return resp.Error @@ -359,7 +369,7 @@ func TestRun_EmitsOnlyReadyOnStartup(t *testing.T) { defer cancel() done := make(chan struct{}) go func() { - _ = m.Run(ctx) + assert.NoError(t, m.Run(ctx)) close(done) }() @@ -598,7 +608,7 @@ func TestTypeValidation_RejectsWrongTypeAndMissingValue(t *testing.T) { test := func(name string, id int, method, params string) { t.Run(name, func(t *testing.T) { - m.handleMessage(requestMessageRaw(id, method, json.RawMessage(params))) + m.handleMessage(requestMessageRaw(t, id, method, json.RawMessage(params))) resp := readCaptureFrame(t, rw) require.NotNil(t, resp.Error) assert.Equal(t, -32602, resp.Error.Code) @@ -620,19 +630,19 @@ func TestLogSetLevel_AcceptsRequestsAndNotifications(t *testing.T) { m, rw, _ := newTestManager(t) // Request form: replies with {"level": "debug"}. - m.handleMessage(requestMessage(1, applog.SetLevelMethod, applog.SetLevelParams{Level: "debug"})) + m.handleMessage(requestMessage(t, 1, applog.SetLevelMethod, applog.SetLevelParams{Level: "debug"})) resp := readCaptureFrame(t, rw) result := decodeResult[map[string]string](t, resp) assert.Equal(t, "debug", result["level"], "log/set-level result (%v)", result) // Invalid level: -32602 in request form. - m.handleMessage(requestMessage(2, applog.SetLevelMethod, applog.SetLevelParams{Level: "shouty"})) + m.handleMessage(requestMessage(t, 2, applog.SetLevelMethod, applog.SetLevelParams{Level: "shouty"})) resp = readCaptureFrame(t, rw) require.NotNil(t, resp.Error, "invalid log/set-level error") assert.Equal(t, -32602, resp.Error.Code, "invalid log/set-level error") // Notification form (no id) — applied silently, no response frame. - m.handleMessage(notificationMessage(applog.SetLevelMethod, applog.SetLevelParams{Level: "info"})) + m.handleMessage(notificationMessage(t, applog.SetLevelMethod, applog.SetLevelParams{Level: "info"})) select { case f := <-rw.responses: require.FailNow(t, fmt.Sprintf("log/set-level notification should not respond (%v)", f)) @@ -643,7 +653,7 @@ func TestLogSetLevel_AcceptsRequestsAndNotifications(t *testing.T) { func TestNotification_IsIgnored(t *testing.T) { m, rw, _ := newTestManager(t) - m.handleMessage(notificationMessage("settings/set-cluster-id", map[string]string{"value": "cluster-zzz"})) + m.handleMessage(notificationMessage(t, "settings/set-cluster-id", map[string]string{"value": "cluster-zzz"})) // State should be unchanged (no response, and a get returns the default). got := callAndDecode[map[string]string](t, m, rw, 1, "settings/get-cluster-id", nil) @@ -673,7 +683,7 @@ func TestShutdownRequest_CancelsRun(t *testing.T) { writePipeRequest(t, client, 7, "shutdown", nil) resp := readPipeFrame(t, client, reader) - assert.True(t, responseWithID(7)(resp), "shutdown response (%v)", resp) + assert.True(t, responseWithID(t, 7)(resp), "shutdown response (%v)", resp) require.Nil(t, resp.Error, "shutdown response (%v)", resp) select { diff --git a/services/nvpair-proxy/activity_test.go b/services/nvpair-proxy/activity_test.go index cdcdcfb5..6232aceb 100644 --- a/services/nvpair-proxy/activity_test.go +++ b/services/nvpair-proxy/activity_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,7 +39,8 @@ func TestStreamedBytes_ReportNodeActivity(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"done":true}`)) + _, err := w.Write([]byte(`{"done":true}`)) + assert.NoError(t, err) })) defer upstream.Close() @@ -118,7 +120,8 @@ func TestRepeatedChunks_AreCoalescedIntoOneReport(t *testing.T) { w.Header().Set("Content-Type", "application/x-ndjson") w.WriteHeader(http.StatusOK) for i := 0; i < chunks; i++ { - _, _ = w.Write([]byte(`{"response":"x"}` + "\n")) + _, err := w.Write([]byte(`{"response":"x"}` + "\n")) + assert.NoError(t, err) if f, ok := w.(http.Flusher); ok { f.Flush() } diff --git a/services/nvpair-proxy/alias_test.go b/services/nvpair-proxy/alias_test.go index a658272d..458041a6 100644 --- a/services/nvpair-proxy/alias_test.go +++ b/services/nvpair-proxy/alias_test.go @@ -51,7 +51,8 @@ func TestLoopbackAlias_UsesPrimaryRouterAndSurvivesPrimaryRebind(t *testing.T) { rec := &recRW{} upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = io.WriteString(w, `{"message":"from-upstream"}`) + _, err := io.WriteString(w, `{"message":"from-upstream"}`) + assert.NoError(t, err) })) defer upstream.Close() @@ -74,8 +75,9 @@ func TestLoopbackAlias_UsesPrimaryRouterAndSurvivesPrimaryRebind(t *testing.T) { t.Helper() resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%d/api/chat", port), "application/json", strings.NewReader(`{"model":"test"}`)) require.NoError(t, err, "POST through port (%v, %v)", port, err) - body, _ := io.ReadAll(resp.Body) - _ = resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) require.Equal(t, http.StatusOK, resp.StatusCode, "port (%v, %v)", port, body) require.Contains(t, string(body), "from-upstream", "port (%v, %v)", port, body) } @@ -98,7 +100,8 @@ func TestLoopbackAlias_UsesPrimaryRouterAndSurvivesPrimaryRebind(t *testing.T) { func TestOccupiedLoopbackAlias_LeavesOwnerAndPrimaryRunning(t *testing.T) { rec := &recRW{} owner := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = io.WriteString(w, "owner") + _, err := io.WriteString(w, "owner") + assert.NoError(t, err) })) owner.Listener = mustLoopbackListener(t) owner.Start() @@ -119,13 +122,14 @@ func TestOccupiedLoopbackAlias_LeavesOwnerAndPrimaryRunning(t *testing.T) { resp, err := http.Get(owner.URL) require.NoError(t, err, "existing owner was disrupted") - body, _ := io.ReadAll(resp.Body) - _ = resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.NoError(t, resp.Body.Close()) require.Equal(t, "owner", string(body), "existing owner response (%v)", body) if conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", primaryPort), time.Second); err != nil { require.FailNow(t, fmt.Sprintf("primary stopped after alias conflict: %v", err)) } else { - _ = conn.Close() + require.NoError(t, conn.Close()) } if !rec.has(ollamaHostAliasBlockedID) || !rec.has(`"severity":"warning"`) || !rec.has(`"action":"none"`) || !rec.has("No process was stopped") { rec.mu.Lock() @@ -167,7 +171,7 @@ func TestAliasSelfTarget_MatchesBoundLoopbackAddressNotPortAlone(t *testing.T) { t.Skipf("no second loopback address available on this host: %v", err) } aliasPort := probe.Addr().(*net.TCPAddr).Port - _ = probe.Close() + require.NoError(t, probe.Close()) disc := NewDiscovery() disc.AddManual(Node{ID: "alias-self", Addresses: []string{"127.0.0.2"}, Port: aliasPort}) @@ -238,11 +242,11 @@ func TestLoopbackAlias_OwnsBothLocalhostFamilies(t *testing.T) { t.Skipf("IPv6 loopback unavailable: %v", err) } candidate := ipv6.Addr().(*net.TCPAddr).Port - _ = ipv6.Close() + require.NoError(t, ipv6.Close()) ipv4, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", candidate)) if err == nil { port = candidate - _ = ipv4.Close() + require.NoError(t, ipv4.Close()) } } require.NotEqual(t, 0, port, "could not find a port free on both loopback families") @@ -260,7 +264,7 @@ func TestLoopbackAlias_OwnsBothLocalhostFamilies(t *testing.T) { } { conn, err := net.DialTimeout("tcp", address, time.Second) require.NoError(t, err, "dial reserved alias (%v, %v)", address, err) - _ = conn.Close() + require.NoError(t, conn.Close()) } } @@ -275,7 +279,7 @@ func TestDualAliasBind_IsAtomic(t *testing.T) { if err != nil { t.Skipf("matching IPv4 loopback port unavailable: %v", err) } - _ = probe.Close() + require.NoError(t, probe.Close()) p := newTestProxy(ollamaOnlyProfile(t), NewCodec(rwNop{}), NewDiscovery(), freeTCPPort(t)) require.NoError(t, p.soleFacade().setLoopbackAlias(fmt.Sprintf("127.0.0.1:%d", port))) @@ -285,7 +289,7 @@ func TestDualAliasBind_IsAtomic(t *testing.T) { require.Nil(t, p.soleFacade().aliasAltLn, "partial localhost ownership survived an alternate-family bind failure") rebound, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) require.NoError(t, err, "IPv4 alias was not released after atomic bind failure") - _ = rebound.Close() + require.NoError(t, rebound.Close()) } func mustLoopbackListener(t *testing.T) net.Listener { diff --git a/services/nvpair-proxy/choose_reachable_test.go b/services/nvpair-proxy/choose_reachable_test.go index f0720953..2ae67466 100644 --- a/services/nvpair-proxy/choose_reachable_test.go +++ b/services/nvpair-proxy/choose_reachable_test.go @@ -15,6 +15,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/clustertrust" @@ -49,7 +50,8 @@ func waitForTarget(t *testing.T, p *Proxy, n Node, want string) { // countingChooser installs a target chooser that records how many connection // attempts routing makes and whether they succeed, so a test can assert on // confirmation behaviour without opening sockets. -func countingChooser(p *Proxy, accept bool) *atomic.Int32 { +func countingChooser(t *testing.T, p *Proxy, accept bool) *atomic.Int32 { + t.Helper() var dials atomic.Int32 p.soleFacade().targets = reach.NewChooser(reach.WithDial( func(_, _ string, _ time.Duration) (net.Conn, error) { @@ -58,7 +60,7 @@ func countingChooser(p *Proxy, accept bool) *atomic.Int32 { return nil, net.ErrClosed } c1, c2 := net.Pipe() - _ = c2.Close() + assert.NoError(t, c2.Close()) return c1, nil })) return &dials @@ -76,12 +78,13 @@ func TestChooseReachable_FailsOverForPinnedPeer(t *testing.T) { p.soleFacade().targets = reach.NewChooser(reach.WithDial( func(_, address string, _ time.Duration) (net.Conn, error) { dials.Add(1) - host, _, _ := net.SplitHostPort(address) + host, _, err := net.SplitHostPort(address) + assert.NoError(t, err) if host != reachable { return nil, net.ErrClosed } c1, c2 := net.Pipe() - _ = c2.Close() + assert.NoError(t, c2.Close()) return c1, nil })) @@ -102,7 +105,7 @@ func TestChooseReachable_FailsOverForPinnedPeer(t *testing.T) { func TestChooseReachable_ProbesPlainMultiHomed(t *testing.T) { p := testProxy(anyProfile(t), NewDiscovery(), 11435) - dials := countingChooser(p, true) + dials := countingChooser(t, p, true) n := Node{ ID: "manual-a", Port: 11434, @@ -124,12 +127,13 @@ func TestTargetURL_FailsOverToAReachableAddress(t *testing.T) { p := testProxy(anyProfile(t), NewDiscovery(), 11435) p.soleFacade().targets = reach.NewChooser(reach.WithDial( func(_, address string, _ time.Duration) (net.Conn, error) { - host, _, _ := net.SplitHostPort(address) + host, _, err := net.SplitHostPort(address) + assert.NoError(t, err) if host != reachable { return nil, net.ErrClosed } c1, c2 := net.Pipe() - _ = c2.Close() + assert.NoError(t, c2.Close()) return c1, nil })) @@ -175,11 +179,13 @@ func (f *fakeNetwork) accept(address string) { f.accepting = address } -func (f *fakeNetwork) install(p *Proxy) { +func (f *fakeNetwork) install(t *testing.T, p *Proxy) { + t.Helper() p.soleFacade().targets = reach.NewChooser(reach.WithDial( func(_, address string, _ time.Duration) (net.Conn, error) { f.dials.Add(1) - host, _, _ := net.SplitHostPort(address) + host, _, err := net.SplitHostPort(address) + assert.NoError(t, err) f.mu.Lock() accepting := f.accepting f.mu.Unlock() @@ -187,7 +193,7 @@ func (f *fakeNetwork) install(p *Proxy) { return nil, net.ErrClosed } c1, c2 := net.Pipe() - _ = c2.Close() + assert.NoError(t, c2.Close()) return c1, nil })) } @@ -208,7 +214,7 @@ func confirmedDeadPeer(t *testing.T, replacement string) (*Proxy, Node, *fakeNet disc.AddManual(n) p := testProxy(anyProfile(t), disc, 11435) fake := &fakeNetwork{accepting: confirmed} - fake.install(p) + fake.install(t, p) waitForTarget(t, p, n, net.JoinHostPort(confirmed, strconv.Itoa(n.Port))) return p, n, fake diff --git a/services/nvpair-proxy/cors_generic_test.go b/services/nvpair-proxy/cors_generic_test.go index fb149498..17d476a4 100644 --- a/services/nvpair-proxy/cors_generic_test.go +++ b/services/nvpair-proxy/cors_generic_test.go @@ -21,16 +21,16 @@ func TestCORSPolicy_IsIndependentOfEngineIdentity(t *testing.T) { upstream := corsEngine(t, tc, allowed, http.StatusOK) defer upstream.Close() proxy := proxyForCORSTargets(t, tc, upstream) - for _, method := range []string{http.MethodOptions, http.MethodGet} { - rec := httptest.NewRecorder() - proxy.handlePlain(rec, corsRequest(method, "/future-api", "https://app.test")) - wantStatus := http.StatusOK - if method == http.MethodOptions { - wantStatus = http.StatusNoContent - } - require.Equal(t, wantStatus, rec.Code, " (%v)", method) - require.Equal(t, allowed, rec.Header().Get("Access-Control-Allow-Origin"), " (%v)", method) + request := func(name, method string, wantStatus int) { + t.Run(name, func(t *testing.T) { + rec := httptest.NewRecorder() + proxy.handlePlain(rec, corsRequest(method, "/future-api", "https://app.test")) + require.Equal(t, wantStatus, rec.Code) + require.Equal(t, allowed, rec.Header().Get("Access-Control-Allow-Origin")) + }) } + request("preflight", http.MethodOptions, http.StatusNoContent) + request("ordinary request", http.MethodGet, http.StatusOK) }) } test("engine grants browser access", "https://app.test") diff --git a/services/nvpair-proxy/cors_test.go b/services/nvpair-proxy/cors_test.go index 82653d7c..5128e830 100644 --- a/services/nvpair-proxy/cors_test.go +++ b/services/nvpair-proxy/cors_test.go @@ -241,6 +241,7 @@ func TestCORS_AppliesPolicyChangesToOrdinaryOptionsRequests(t *testing.T) { require.Equal(t, 403, rec.Code, "cached obsolete permission") }) } + func TestCORSModelList_StripsCredentialsAndDoesNotRedirect(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { var leaked atomic.Int32 @@ -284,15 +285,18 @@ func TestCORSModelList_StripsCredentialsAndDoesNotRedirect(t *testing.T) { require.Equal(t, int32(0), leaked.Load(), "followed aggregate redirect") }) } + func TestCORSPairedIngress_PreservesPolicyAndIsTerminal(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { aDir, bDir := t.TempDir(), t.TempDir() clustertrusttest.Join(t, aDir, "cluster", "a") clustertrusttest.Join(t, bDir, "cluster", "b") pin := func(dst, src, id string) { + t.Helper() pem, err := os.ReadFile(filepath.Join(src, "node.crt")) require.NoError(t, err) - body, _ := json.Marshal(map[string]string{"nodeUuid": id, "certPem": string(pem)}) + body, err := json.Marshal(map[string]string{"nodeUuid": id, "certPem": string(pem)}) + require.NoError(t, err) require.NoError(t, os.MkdirAll(filepath.Join(dst, "trusted"), 0700)) require.NoError(t, os.WriteFile(filepath.Join(dst, "trusted", id+".json"), body, 0600)) } @@ -358,7 +362,8 @@ func TestCORS_EnforcesPolicyForBrowserRequests(t *testing.T) { none := proxyForCORSTargets(t, tc) unavailable := httptest.NewServer(http.HandlerFunc(none.handlePlain)) defer unavailable.Close() - data, _ := json.Marshal(map[string]string{"allowed": allowed.URL, "denied": denied.URL, "proxy": proxy.URL, "engine": engine.URL, "unavailable": unavailable.URL, "models": tc.modelListPath}) + data, err := json.Marshal(map[string]string{"allowed": allowed.URL, "denied": denied.URL, "proxy": proxy.URL, "engine": engine.URL, "unavailable": unavailable.URL, "models": tc.modelListPath}) + require.NoError(t, err) require.NoError(t, os.WriteFile(file, data, 0600)) deadline := time.After(60 * time.Second) tick := time.NewTicker(100 * time.Millisecond) @@ -432,7 +437,7 @@ func TestCORS_PreservesExternalEnginePolicy(t *testing.T) { resp, err := client.Do(req) require.NoError(t, err) defer func() { - assert.NoError(t, resp.Body.Close()) + require.NoError(t, resp.Body.Close()) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) @@ -510,7 +515,7 @@ func TestCORSStreamingResponse_IsPreserved(t *testing.T) { resp, err := client.Do(req) require.NoError(t, err) defer func() { - assert.NoError(t, resp.Body.Close()) + require.NoError(t, resp.Body.Close()) }() first := make([]byte, 5) _, err = io.ReadFull(resp.Body, first) diff --git a/services/nvpair-proxy/e2e_test.go b/services/nvpair-proxy/e2e_test.go index 7a5ef38b..72a97637 100644 --- a/services/nvpair-proxy/e2e_test.go +++ b/services/nvpair-proxy/e2e_test.go @@ -6,6 +6,7 @@ package main import ( "bufio" "encoding/json" + "errors" "fmt" "io" "net" @@ -69,17 +70,21 @@ type e2eFrame struct { Error json.RawMessage `json:"error"` } -func e2eReadFrames(r io.Reader, out chan<- e2eFrame) { +func e2eReadFrames(t *testing.T, r io.Reader, out chan<- e2eFrame) { + t.Helper() defer close(out) sc := bufio.NewScanner(r) sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) for sc.Scan() { var f e2eFrame - if err := json.Unmarshal(sc.Bytes(), &f); err != nil { + if !assert.NoError(t, json.Unmarshal(sc.Bytes(), &f)) { continue } out <- f } + if err := sc.Err(); !errors.Is(err, os.ErrClosed) { + assert.NoError(t, err) + } } func e2eSend(t *testing.T, w io.Writer, id int, method string, params any) { @@ -88,8 +93,9 @@ func e2eSend(t *testing.T, w io.Writer, id int, method string, params any) { if params != nil { msg["params"] = params } - data, _ := json.Marshal(msg) - _, err := w.Write(append(data, '\n')) + data, err := json.Marshal(msg) + require.NoError(t, err) + _, err = w.Write(append(data, '\n')) require.NoError(t, err, "send (%v, %v)", method, err) } @@ -163,7 +169,8 @@ func e2eEnabledPort(t *testing.T, frames *e2eInbox, id string, timeout time.Dura var rpcErr struct { Code int `json:"code"` } - if json.Unmarshal(f.Error, &rpcErr) == nil && rpcErr.Code == codeFacadeBindFailed { + require.NoError(t, json.Unmarshal(f.Error, &rpcErr)) + if rpcErr.Code == codeFacadeBindFailed { return 0, true } require.FailNow(t, fmt.Sprintf("facade/enable returned error: %s", f.Error)) @@ -267,10 +274,12 @@ func TestProxy_FailsOverAcrossNodesOverStdio(t *testing.T) { })) defer busy.Close() good := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - b, _ := io.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) + assert.NoError(t, err) gotBody = string(b) w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"ok":true}`) + _, err = io.WriteString(w, `{"ok":true}`) + assert.NoError(t, err) })) defer good.Close() @@ -291,7 +300,7 @@ func TestProxy_FailsOverAcrossNodesOverStdio(t *testing.T) { }() source := make(chan e2eFrame, 256) - go e2eReadFrames(stdout, source) + go e2eReadFrames(t, stdout, source) frames := &e2eInbox{frames: source} // Retried on a bind race: e2eFreePort can only probe-then-close, and a @@ -327,7 +336,7 @@ func TestProxy_FailsOverAcrossNodesOverStdio(t *testing.T) { fmt.Sprintf("http://127.0.0.1:%d%s", port, tc.inferencePath), "application/json", strings.NewReader(body)) require.NoError(t, err, "inference POST") - defer resp.Body.Close() + defer func() { require.NoError(t, resp.Body.Close()) }() require.Equal(t, http.StatusOK, resp.StatusCode, "status") assert.Equal(t, "", resp.Header.Get("Access-Control-Allow-Origin"), "Access-Control-Allow-Origin") assert.Equal(t, body, gotBody, "healthy upstream got body (%v, %v)", gotBody, body) diff --git a/services/nvpair-proxy/failover_test.go b/services/nvpair-proxy/failover_test.go index 10783822..9e31a1a6 100644 --- a/services/nvpair-proxy/failover_test.go +++ b/services/nvpair-proxy/failover_test.go @@ -146,7 +146,8 @@ func TestHandleHTTP_PreservesEngineCORSPolicy(t *testing.T) { w.Header().Set("Access-Control-Allow-Origin", "https://app.example") w.Header().Set("Access-Control-Allow-Credentials", "true") w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err := io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer engine.Close() @@ -168,7 +169,8 @@ func TestHandleHTTP_PreservesEngineCredentialsWithoutOrigin(t *testing.T) { engine := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Access-Control-Allow-Credentials", "true") w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err := io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer engine.Close() @@ -190,10 +192,12 @@ func TestHandleHTTP_ForwardsRequestToSingleNode(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { var gotBody string good := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - b, _ := io.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) + assert.NoError(t, err) gotBody = string(b) w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err = io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer good.Close() @@ -218,7 +222,8 @@ func TestHandleHTTP_DoesNotRetryBadRequestResponse(t *testing.T) { bad := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hits++ w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, `{"error":"bad request"}`) + _, err := io.WriteString(w, `{"error":"bad request"}`) + assert.NoError(t, err) })) defer bad.Close() other := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -258,16 +263,19 @@ func TestHandleHTTP_FailsOverOnServiceUnavailable(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { busy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) - io.WriteString(w, `{"error":"loading model"}`) + _, err := io.WriteString(w, `{"error":"loading model"}`) + assert.NoError(t, err) })) defer busy.Close() var gotBody string good := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - b, _ := io.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) + assert.NoError(t, err) gotBody = string(b) w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err = io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer good.Close() @@ -318,12 +326,14 @@ func TestHandleHTTP_FailsOverOnNotFoundForInferenceRequests(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { missing := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) - io.WriteString(w, `{"error":"model not found"}`) + _, err := io.WriteString(w, `{"error":"model not found"}`) + assert.NoError(t, err) })) defer missing.Close() has := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err := io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer has.Close() @@ -400,7 +410,8 @@ func TestHandleHTTP_AggregatesNativeModelList(t *testing.T) { assert.Empty(t, r.Header.Get("Cookie"), "client credentials leaked to fan-out target") entered <- struct{}{} <-release - _, _ = io.WriteString(w, body) + _, err := io.WriteString(w, body) + assert.NoError(t, err) })) } a := server(`{"models":[{"name":"a","model":"a","digest":"a-only"},{"name":"shared","digest":"first"}]}`) @@ -408,7 +419,8 @@ func TestHandleHTTP_AggregatesNativeModelList(t *testing.T) { b := server(`{"models":[{"name":"shared:latest","model":"shared:latest","digest":"second"},{"name":"c","model":"c","digest":"c-only"}]}`) defer b.Close() malformed := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = io.WriteString(w, `{"models":null}`) + _, err := io.WriteString(w, `{"models":null}`) + assert.NoError(t, err) })) defer malformed.Close() down := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) @@ -481,7 +493,8 @@ func TestHandleHTTP_AggregatesOpenAIModelList(t *testing.T) { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method, "upstream request") assert.Equal(t, "/v1/models", r.URL.Path, "upstream request") - _, _ = io.WriteString(w, body) + _, err := io.WriteString(w, body) + assert.NoError(t, err) })) } a := serve(`{"object":"list","data":[{"id":"a","owned_by":"a"},{"id":"shared","owned_by":"first"}]}`) @@ -571,7 +584,8 @@ func TestHandleHTTP_UsesEngineSpecificUpstreamModelListPath(t *testing.T) { assert.Equal(t, http.MethodGet, r.Method, "upstream request method") assert.Equal(t, "/models", r.URL.Path, "upstream request path") assert.Equal(t, "scope=all", r.URL.RawQuery, "upstream request query") - _, _ = io.WriteString(w, `{"data":[{"id":"remapped"}]}`) + _, err := io.WriteString(w, `{"data":[{"id":"remapped"}]}`) + assert.NoError(t, err) })) defer upstream.Close() @@ -595,7 +609,8 @@ func TestHandleHTTP_DistinguishesEmptyModelListFromUnavailableNodes(t *testing.T forEachEngine(t, func(t *testing.T, tc engineCase) { serveEmpty := func() *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = io.WriteString(w, tc.emptyModelList) + _, err := io.WriteString(w, tc.emptyModelList) + assert.NoError(t, err) })) } @@ -646,7 +661,8 @@ func TestHandleHTTP_RoutesOnlyToNodesAdvertisingRequestedModel(t *testing.T) { defer unknown.Close() match := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { matchHits++ - body, _ := io.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) + assert.NoError(t, err) assert.Equal(t, tc.inferenceBody(), string(body), "matching node got body (%v)", body) w.WriteHeader(http.StatusOK) })) diff --git a/services/nvpair-proxy/firstbody_test.go b/services/nvpair-proxy/firstbody_test.go index 4ccbb64f..50b46cfb 100644 --- a/services/nvpair-proxy/firstbody_test.go +++ b/services/nvpair-proxy/firstbody_test.go @@ -81,10 +81,12 @@ func TestHandleHTTP_FailsOverWhenHeadersArriveWithoutBody(t *testing.T) { var servedBody string good := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - b, _ := io.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) + assert.NoError(t, err) servedBody = string(b) w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err = io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer good.Close() @@ -153,7 +155,8 @@ func TestHandleHTTP_PreservesFirstBodyByteWhenCommittingResponse(t *testing.T) { const payload = `{"response":"whole body intact","done":true}` good := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - io.WriteString(w, payload) + _, err := io.WriteString(w, payload) + assert.NoError(t, err) })) defer good.Close() diff --git a/services/nvpair-proxy/portstore_test.go b/services/nvpair-proxy/portstore_test.go index 3cb8e4d6..3e92a250 100644 --- a/services/nvpair-proxy/portstore_test.go +++ b/services/nvpair-proxy/portstore_test.go @@ -114,7 +114,7 @@ func TestSetPort_Rebinds(t *testing.T) { // New port is now serving. conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", portB), 2*time.Second) require.NoError(t, err, "new port (%v, %v)", portB, err) - conn.Close() + require.NoError(t, conn.Close()) // Old port stopped accepting. if c, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", portA), 500*time.Millisecond); err == nil { @@ -151,9 +151,9 @@ func TestSetPortPersistenceFailure_PreservesListener(t *testing.T) { require.Error(t, proxy.soleFacade().setPort(portB), "persistence failure reported success") conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", portA), time.Second) require.NoError(t, err, "old listener lost") - _ = conn.Close() + require.NoError(t, conn.Close()) next, err := net.Listen("tcp", fmt.Sprintf(":%d", portB)) require.NoError(t, err, "failed candidate listener leaked") - _ = next.Close() + require.NoError(t, next.Close()) }) } diff --git a/services/nvpair-proxy/retry_test.go b/services/nvpair-proxy/retry_test.go index daddd31b..dc294881 100644 --- a/services/nvpair-proxy/retry_test.go +++ b/services/nvpair-proxy/retry_test.go @@ -42,7 +42,8 @@ func newCountingServer(t *testing.T, status int, body string) (serverURL string, srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { n.Add(1) w.WriteHeader(status) - io.WriteString(w, body) + _, err := io.WriteString(w, body) + assert.NoError(t, err) })) t.Cleanup(srv.Close) return srv.URL, func() int { return int(n.Load()) } @@ -201,11 +202,13 @@ func TestHandleHTTP_RetriesTheOnlyOwner(t *testing.T) { flaky := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if hits.Add(1) <= 2 { w.WriteHeader(http.StatusServiceUnavailable) - io.WriteString(w, `{"error":"loading model"}`) + _, err := io.WriteString(w, `{"error":"loading model"}`) + assert.NoError(t, err) return } w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err := io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer flaky.Close() @@ -425,7 +428,8 @@ func TestHandleHTTP_AbortsAttemptWhenTargetLeavesDiscovery(t *testing.T) { good := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err := io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) defer good.Close() diff --git a/services/nvpair-proxy/twofacade_test.go b/services/nvpair-proxy/twofacade_test.go index 71764fe8..b35e1bb3 100644 --- a/services/nvpair-proxy/twofacade_test.go +++ b/services/nvpair-proxy/twofacade_test.go @@ -388,9 +388,7 @@ func TestFacades_AddressNotificationsToTheirEngine(t *testing.T) { var msg struct { Method string `json:"method"` } - if json.Unmarshal(line, &msg) != nil { - continue - } + require.NoError(t, json.Unmarshal(line, &msg)) engine, bare := engines.SplitAddressedMethod(msg.Method) if bare != "ready" { continue diff --git a/services/nvpair-proxy/workload_test.go b/services/nvpair-proxy/workload_test.go index da7bee1e..f68ea9dc 100644 --- a/services/nvpair-proxy/workload_test.go +++ b/services/nvpair-proxy/workload_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -74,7 +75,8 @@ func TestHandleHTTP_PublishesWorkloadBeforeFirstResponseByte(t *testing.T) { } <-release // no response byte until the test releases us w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"done":true}`) + _, err := io.WriteString(w, `{"done":true}`) + assert.NoError(t, err) })) // Deferred order matters (LIFO): doRelease runs BEFORE slow.Close so // the blocked upstream handler is unblocked before the server is torn diff --git a/services/nvpair-proxy/zombie_test.go b/services/nvpair-proxy/zombie_test.go index 374e0ab5..07d50a55 100644 --- a/services/nvpair-proxy/zombie_test.go +++ b/services/nvpair-proxy/zombie_test.go @@ -78,7 +78,8 @@ func TestHandleHTTP_MarksWorkloadCancelledAfterClientWriteFailure(t *testing.T) upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - io.WriteString(w, `{"model":"llama","response":"partial tokens before the client vanished","done":false}`) + _, err := io.WriteString(w, `{"model":"llama","response":"partial tokens before the client vanished","done":false}`) + assert.NoError(t, err) })) defer upstream.Close() @@ -123,7 +124,8 @@ func TestHandleHTTP_EmitsOneTerminalEventAfterClientDisconnect(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) if f, ok := w.(http.Flusher); ok { - io.WriteString(w, `{"response":"first chunk","done":false}`+"\n") + _, err := io.WriteString(w, `{"response":"first chunk","done":false}`+"\n") + assert.NoError(t, err) f.Flush() } select { @@ -342,7 +344,7 @@ func TestHandleHTTP_BoundsRealSocketWrites(t *testing.T) { // Read the status line only — enough to confirm the response committed and // started streaming — then STOP reading so the proxy's send buffer backs // up. A read deadline guards against a hang if the proxy never responds. - _ = conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + require.NoError(t, conn.SetReadDeadline(time.Now().Add(5*time.Second))) statusLine, err := bufio.NewReader(conn).ReadString('\n') require.NoError(t, err, "read status line") require.Contains(t, statusLine, "200", "unexpected status line") @@ -430,7 +432,7 @@ func TestHandleHTTP_BoundsRealSocketFlushes(t *testing.T) { // Read the status line only, then stop reading so the proxy's send buffer // backs up and a Flush blocks. - _ = conn.SetReadDeadline(time.Now().Add(5 * time.Second)) + require.NoError(t, conn.SetReadDeadline(time.Now().Add(5*time.Second))) statusLine, err := bufio.NewReader(conn).ReadString('\n') require.NoError(t, err, "read status line") require.Contains(t, statusLine, "200", "unexpected status line") @@ -495,8 +497,10 @@ func TestHandleHTTP_EmitsTerminalEventWhenUpstreamDiesMidStream(t *testing.T) { } } chunk := `{"response":"partial tokens","done":false}` - io.WriteString(c, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nTransfer-Encoding: chunked\r\n\r\n") - fmt.Fprintf(c, "%x\r\n%s\r\n", len(chunk), chunk) + _, err := io.WriteString(c, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nTransfer-Encoding: chunked\r\n\r\n") + assert.NoError(t, err) + _, err = fmt.Fprintf(c, "%x\r\n%s\r\n", len(chunk), chunk) + assert.NoError(t, err) c.Close() }(conn) } diff --git a/services/nvpair-tui/rpc/client_test.go b/services/nvpair-tui/rpc/client_test.go index 5d508ade..a5b82d80 100644 --- a/services/nvpair-tui/rpc/client_test.go +++ b/services/nvpair-tui/rpc/client_test.go @@ -41,7 +41,7 @@ func TestCodec_RoundTripsJSONRPCFrames(t *testing.T) { b := NewCodec(c2, c2) go func() { - _ = a.Write(&Message{JSONRPC: "2.0", Method: "hello", Params: json.RawMessage(`{"x":1}`)}) + assert.NoError(t, a.Write(&Message{JSONRPC: "2.0", Method: "hello", Params: json.RawMessage(`{"x":1}`)})) }() msg, err := b.Read() @@ -57,10 +57,10 @@ func TestClientCall_MatchesResponse(t *testing.T) { // Server: read the request and echo a result tagged with its id. go func() { req, err := server.Read() - if err != nil { + if !assert.NoError(t, err) { return } - _ = server.Write(&Message{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(`{"pong":true}`)}) + assert.NoError(t, server.Write(&Message{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(`{"pong":true}`)})) }() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -74,10 +74,10 @@ func TestClientCall_SurfacesRPCError(t *testing.T) { client, server, _ := newPair(t) go func() { req, err := server.Read() - if err != nil { + if !assert.NoError(t, err) { return } - _ = server.Write(&Message{JSONRPC: "2.0", ID: req.ID, Error: &RPCError{Code: -32000, Message: "boom"}}) + assert.NoError(t, server.Write(&Message{JSONRPC: "2.0", ID: req.ID, Error: &RPCError{Code: -32000, Message: "boom"}})) }() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -122,7 +122,7 @@ func TestClientCall_RefusesBeforeWriting(t *testing.T) { func TestClient_DeliversNotifications(t *testing.T) { client, server, _ := newPair(t) go func() { - _ = server.Write(&Message{JSONRPC: "2.0", Method: "errors:update", Params: json.RawMessage(`[]`)}) + assert.NoError(t, server.Write(&Message{JSONRPC: "2.0", Method: "errors:update", Params: json.RawMessage(`[]`)})) }() select { @@ -136,7 +136,7 @@ func TestClient_DeliversNotifications(t *testing.T) { func TestClientDisconnect_ClosesNotifications(t *testing.T) { client, _, srv := newPair(t) - srv.Close() // broker drops the connection -> client read loop hits EOF + require.NoError(t, srv.Close()) // broker drops the connection -> client read loop hits EOF done := make(chan struct{}) go func() { diff --git a/services/nvpair-tui/supervisor_test.go b/services/nvpair-tui/supervisor_test.go index 8b96e30a..d4cc8b02 100644 --- a/services/nvpair-tui/supervisor_test.go +++ b/services/nvpair-tui/supervisor_test.go @@ -7,6 +7,7 @@ import ( "bufio" "context" "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -23,11 +24,17 @@ import ( // the actual broker built. func TestMain(m *testing.M) { if os.Getenv("NVPAIR_TUI_FAKE_BROKER") == "1" { - runFakeBroker() + if err := runFakeBroker(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } return } if os.Getenv("NVPAIR_TUI_SILENT_BROKER") == "1" { - runSilentBroker() + if err := runSilentBroker(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } return } os.Exit(m.Run()) @@ -44,8 +51,11 @@ const fakeBrokerPreemptedExit = 3 // An engine:prepare-shutdown from the supervisor is a failure, reported through // the exit code: the broker stops the proxy before the engines, and a client // stopping the engines first reverses that. -func runFakeBroker() { - fmt.Fprintln(os.Stdout, `{"jsonrpc":"2.0","method":"app:ready","params":{"version":"fake"}}`) +func runFakeBroker() error { + if _, err := fmt.Fprintln(os.Stdout, `{"jsonrpc":"2.0","method":"app:ready","params":{"version":"fake"}}`); err != nil { + return err + } + var decodeErr error sc := bufio.NewScanner(os.Stdin) for sc.Scan() { var m struct { @@ -53,25 +63,30 @@ func runFakeBroker() { Method string `json:"method"` } if err := json.Unmarshal(sc.Bytes(), &m); err != nil { + decodeErr = errors.Join(decodeErr, err) continue } switch m.Method { case "engine:prepare-shutdown": os.Exit(fakeBrokerPreemptedExit) case "shutdown": - fmt.Fprintf(os.Stdout, `{"jsonrpc":"2.0","id":%s,"result":null}`+"\n", m.ID) - os.Exit(0) + _, err := fmt.Fprintf(os.Stdout, `{"jsonrpc":"2.0","id":%s,"result":null}`+"\n", m.ID) + return errors.Join(decodeErr, err) } } + return errors.Join(decodeErr, sc.Err()) } // runSilentBroker handshakes and then answers nothing, standing in for a broker // that has stopped responding. -func runSilentBroker() { - fmt.Fprintln(os.Stdout, `{"jsonrpc":"2.0","method":"app:ready","params":{"version":"fake"}}`) +func runSilentBroker() error { + if _, err := fmt.Fprintln(os.Stdout, `{"jsonrpc":"2.0","method":"app:ready","params":{"version":"fake"}}`); err != nil { + return err + } sc := bufio.NewScanner(os.Stdin) for sc.Scan() { } + return sc.Err() } func TestResolveBrokerPath_UsesConfiguredOverride(t *testing.T) { @@ -129,10 +144,16 @@ func TestSupervisor_WaitsForReadyAndShutsDownBroker(t *testing.T) { sup, err := Spawn(ctx, os.Args[0]) require.NoError(t, err, "spawn") + stderrDone := make(chan struct{}) + var stderrText string + var stderrErr error go func() { + defer close(stderrDone) sc := bufio.NewScanner(sup.Stderr) for sc.Scan() { + stderrText += sc.Text() } + stderrErr = sc.Err() }() select { @@ -155,4 +176,13 @@ func TestSupervisor_WaitsForReadyAndShutsDownBroker(t *testing.T) { } assert.NotEqual(t, fakeBrokerPreemptedExit, sup.cmd.ProcessState.ExitCode(), "the supervisor stopped the engines itself, ahead of the broker's proxy-first teardown") + select { + case <-stderrDone: + assert.Empty(t, stderrText, "fake broker stderr") + if !errors.Is(stderrErr, os.ErrClosed) { + require.NoError(t, stderrErr) + } + case <-time.After(5 * time.Second): + require.FailNow(t, "fake broker stderr did not close") + } } diff --git a/services/nvpair-tui/ui/catalog_test.go b/services/nvpair-tui/ui/catalog_test.go index 5aa09547..c2f06666 100644 --- a/services/nvpair-tui/ui/catalog_test.go +++ b/services/nvpair-tui/ui/catalog_test.go @@ -39,13 +39,13 @@ func catalogBroker(t *testing.T) (*rpc.Client, <-chan map[string]string) { return } var params map[string]string - _ = json.Unmarshal(req.Params, ¶ms) + assert.NoError(t, json.Unmarshal(req.Params, ¶ms)) calls <- params result := `{"models":[{"name":"ggml-org/browse-GGUF:Q4_K_M"}],"searchable":true}` if q := params["query"]; q != "" { result = `{"models":[{"name":"found/` + q + `-GGUF:Q4_K_M"}],"searchable":true,"query":"` + q + `"}` } - _ = broker.Write(&rpc.Message{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(result)}) + assert.NoError(t, broker.Write(&rpc.Message{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(result)})) } }() return client, calls diff --git a/services/nvpair-tui/ui/demo_test.go b/services/nvpair-tui/ui/demo_test.go index 2b3707f0..71123f0d 100644 --- a/services/nvpair-tui/ui/demo_test.go +++ b/services/nvpair-tui/ui/demo_test.go @@ -316,9 +316,11 @@ func TestProbe_AsksTheDispatcherCorrectlyAndKeepsOnlyGenerativeModels(t *testing raw, err := os.ReadFile(argsFile) require.NoError(t, err) args := strings.Fields(string(raw)) - for _, want := range []string{"--backend", "ollama", "--port", "11434", "--list-models"} { - assert.Contains(t, args, want, "dispatcher argument") - } + assert.Contains(t, args, "--backend", "dispatcher argument") + assert.Contains(t, args, "ollama", "dispatcher argument") + assert.Contains(t, args, "--port", "dispatcher argument") + assert.Contains(t, args, "11434", "dispatcher argument") + assert.Contains(t, args, "--list-models", "dispatcher argument") } func TestProbe_TreatsAnUnreachableEngineAsNoTargets(t *testing.T) { diff --git a/services/nvpair-tui/ui/engineswire_test.go b/services/nvpair-tui/ui/engineswire_test.go index 716a8a86..ab212834 100644 --- a/services/nvpair-tui/ui/engineswire_test.go +++ b/services/nvpair-tui/ui/engineswire_test.go @@ -95,7 +95,7 @@ func TestPullDeadline_ReportsDetachedNotSilence(t *testing.T) { require.NotNil(t, msg, "a pull that outran its deadline produced no message at all") op, ok := msg.(engineOpMsg) require.True(t, ok, "result must be engineOpMsg") - assert.NoError(t, op.err, "a still-running download must not be reported as failed") + require.NoError(t, op.err, "a still-running download must not be reported as failed") assert.True(t, op.detached, "a still-running download must not be reported as complete") } @@ -115,19 +115,32 @@ func TestDeadlineLeniency_TracksOperationLength(t *testing.T) { // Start and restart wait on the engine's readiness probe, which the // manifests allow up to ten minutes, so a deadline on either is a slow // reply rather than a failed start. - for _, op := range []string{"pull", "load", "install", "uninstall", "start", "restart"} { - result, ok := classifyOpResult("x", "ollama", op, context.DeadlineExceeded).(engineOpMsg) - require.True(t, ok, "%s: unexpected message type", op) - assert.True(t, result.detached, "%s timing out should be reported as still running", op) - assert.NoError(t, result.err, "%s is still running", op) + longRunning := func(name, op string) { + t.Run(name, func(t *testing.T) { + result, ok := classifyOpResult("x", "ollama", op, context.DeadlineExceeded).(engineOpMsg) + require.True(t, ok, "result must be engineOpMsg") + assert.True(t, result.detached, "a timed-out long operation must be reported as still running") + require.NoError(t, result.err, "operation is still running") + }) } - - for _, op := range []string{"unload", "delete", "stop"} { - result, ok := classifyOpResult("x", "ollama", op, context.DeadlineExceeded).(engineOpMsg) - require.True(t, ok, "%s: unexpected message type", op) - assert.False(t, result.detached, "%s: a quick operation that times out has really failed", op) - assert.Error(t, result.err, "%s timing out must not be reported as success", op) + longRunning("pull remains running", "pull") + longRunning("load remains running", "load") + longRunning("install remains running", "install") + longRunning("uninstall remains running", "uninstall") + longRunning("start remains running", "start") + longRunning("restart remains running", "restart") + + quick := func(name, op string) { + t.Run(name, func(t *testing.T) { + result, ok := classifyOpResult("x", "ollama", op, context.DeadlineExceeded).(engineOpMsg) + require.True(t, ok, "result must be engineOpMsg") + assert.False(t, result.detached, "a quick operation that times out has really failed") + assert.Error(t, result.err, "a timeout must not be reported as success") + }) } + quick("unload times out", "unload") + quick("delete times out", "delete") + quick("stop times out", "stop") // A real error is still an error, however long the operation usually takes. result, _ := classifyOpResult("x", "ollama", "pull", errors.New("no such model")).(engineOpMsg) @@ -189,7 +202,7 @@ func TestEveryEngine_HasAModelWire(t *testing.T) { for _, e := range engines.All() { for op := range modelActions { _, err := modelActionWire(e.Name, op, "m") - assert.NoError(t, err, "%s must have a local wire for %s", e.Name, op) + require.NoError(t, err, "%s must have a local wire for %s", e.Name, op) } } _, err := modelActionWire("vllm", "load", "m") diff --git a/services/nvpair-tui/ui/logoutput_test.go b/services/nvpair-tui/ui/logoutput_test.go index d8f62347..7c5d1605 100644 --- a/services/nvpair-tui/ui/logoutput_test.go +++ b/services/nvpair-tui/ui/logoutput_test.go @@ -20,12 +20,14 @@ func TestOwnLog_ReachesTheLogsTab(t *testing.T) { var stderr bytes.Buffer out := &logOutput{fallback: &stderr} - _, _ = out.Write([]byte("before\n")) + _, err := out.Write([]byte("before\n")) + require.NoError(t, err) assert.Equal(t, "before\n", stderr.String(), "a line before the program started should reach stderr") lines := make(chan string, 4) out.attach(lines) - _, _ = out.Write([]byte("one\ntwo\n")) + _, err = out.Write([]byte("one\ntwo\n")) + require.NoError(t, err) for _, want := range []string{"one", "two"} { select { case got := <-lines: @@ -36,7 +38,8 @@ func TestOwnLog_ReachesTheLogsTab(t *testing.T) { } out.detach() - _, _ = out.Write([]byte("after\n")) + _, err = out.Write([]byte("after\n")) + require.NoError(t, err) assert.Equal(t, "before\nafter\n", stderr.String(), "a line after the program ended should reach stderr") } @@ -50,7 +53,8 @@ func TestOwnLog_NeverBlocksOnAFullTab(t *testing.T) { done := make(chan struct{}) go func() { - _, _ = out.Write([]byte("fits\nfull\nstill full\n")) + _, err := out.Write([]byte("fits\nfull\nstill full\n")) + assert.NoError(t, err) close(done) }() select { diff --git a/services/nvpair-tui/ui/model_test.go b/services/nvpair-tui/ui/model_test.go index 82860830..29a06930 100644 --- a/services/nvpair-tui/ui/model_test.go +++ b/services/nvpair-tui/ui/model_test.go @@ -146,9 +146,8 @@ func TestMinimumWidth_KeepsNavigationVisible(t *testing.T) { lines := strings.Split(m.View(), "\n") assert.Contains(t, lines[1], "Logs", "last tab is hidden at minimum width") footer := lines[len(lines)-1] - for _, want := range []string{"quit", "help"} { - assert.Contains(t, footer, want, "footer at minimum width") - } + assert.Contains(t, footer, "quit", "footer at minimum width") + assert.Contains(t, footer, "help", "footer at minimum width") // With an error count the names no longer fit, and every tab must still be // reachable by the number the bar shows for it. diff --git a/services/nvpair-tui/ui/nodedetail_test.go b/services/nvpair-tui/ui/nodedetail_test.go index ca0790be..6a168c5e 100644 --- a/services/nvpair-tui/ui/nodedetail_test.go +++ b/services/nvpair-tui/ui/nodedetail_test.go @@ -33,8 +33,10 @@ func remoteDetail() *nodeDetail { } // discoveryPush builds a discovery:nodes-changed notification. -func discoveryPush(nodes ...availableNode) NotificationMsg { - params, _ := json.Marshal(nodes) +func discoveryPush(t *testing.T, nodes ...availableNode) NotificationMsg { + t.Helper() + params, err := json.Marshal(nodes) + require.NoError(t, err) return NotificationMsg{Msg: &rpc.Message{Method: "discovery:nodes-changed", Params: params}} } @@ -51,7 +53,7 @@ func TestRemoteDetail_FollowsDiscoveryModels(t *testing.T) { d.models = modelsResult{Models: []string{"old-model"}} d.refreshModels() - d.update(discoveryPush(availableNode{ + d.update(discoveryPush(t, availableNode{ HostUUID: "peer", Name: "peer-host", Models: []string{"old-model", "new-model"}, @@ -69,7 +71,7 @@ func TestRemoteDetail_IgnoresOtherNodesDiscovery(t *testing.T) { d.models = modelsResult{Models: []string{"mine"}} d.refreshModels() - d.update(discoveryPush(availableNode{ + d.update(discoveryPush(t, availableNode{ HostUUID: "somebody-else", Name: "other-host", Models: []string{"theirs"}, @@ -110,7 +112,7 @@ func TestLocalDetail_IgnoresDiscoveryModels(t *testing.T) { d.models = modelsResult{Models: []string{"authoritative"}} d.refreshModels() - d.update(discoveryPush(availableNode{ + d.update(discoveryPush(t, availableNode{ HostUUID: "self", Name: "this-host", Models: []string{"stale-summary"}, @@ -379,8 +381,10 @@ func TestSettingsApply_UsesTheNormalizedDraft(t *testing.T) { } // settingsPush builds an engine:settings-changed notification. -func settingsPush(snap enginesettings.Snapshot) NotificationMsg { - params, _ := json.Marshal(snap) +func settingsPush(t *testing.T, snap enginesettings.Snapshot) NotificationMsg { + t.Helper() + params, err := json.Marshal(snap) + require.NoError(t, err) return NotificationMsg{Msg: &rpc.Message{Method: "engine:settings-changed", Params: params}} } @@ -402,7 +406,7 @@ func TestLocalSettingsPush_IsNotDiscarded(t *testing.T) { moved := ollamaSettings() moved.NodeID = uuid moved.Revision = 8 - d.handleNotification(settingsPush(moved).Msg) + d.handleNotification(settingsPush(t, moved).Msg) require.Equal(t, uint64(8), d.settings["ollama"].Revision, "cached revision after a push for this machine") @@ -410,7 +414,7 @@ func TestLocalSettingsPush_IsNotDiscarded(t *testing.T) { other := ollamaSettings() other.NodeID = "some-other-node" other.Revision = 99 - d.handleNotification(settingsPush(other).Msg) + d.handleNotification(settingsPush(t, other).Msg) assert.Equal(t, uint64(8), d.settings["ollama"].Revision, "a peer's snapshot overwrote this machine's") } @@ -959,8 +963,10 @@ func TestSecondSettingsChange_WaitsForTheFirst(t *testing.T) { } // progressPush builds a progress notification from its fields as sent. -func progressPush(method string, fields map[string]any) NotificationMsg { - params, _ := json.Marshal(fields) +func progressPush(t *testing.T, method string, fields map[string]any) NotificationMsg { + t.Helper() + params, err := json.Marshal(fields) + require.NoError(t, err) return NotificationMsg{Msg: &rpc.Message{Method: method, Params: params}} } @@ -968,7 +974,7 @@ func progressPush(method string, fields map[string]any) NotificationMsg { // is not reported on a peer's screen. The payload carries no node, so shown // there it read as the peer installing something. func TestInstallProgress_StaysOnThisMachine(t *testing.T) { - frame := progressPush("engine:install-progress", + frame := progressPush(t, "engine:install-progress", map[string]any{"engine": "ollama", "stage": "downloading", "percent": 40}) peer := remoteDetail() @@ -986,21 +992,21 @@ func TestInstallProgress_StaysOnThisMachine(t *testing.T) { // "(0%)" and "(-1%)". func TestProgressWithoutAPercent_ShowsNone(t *testing.T) { peer := remoteDetail() - peer.update(progressPush("engine:remote-progress", + peer.update(progressPush(t, "engine:remote-progress", map[string]any{"node": "peer", "engine": "ollama", "op": "pull", "stage": "pulling manifest"})) got := peer.status.render() assert.Contains(t, got, "pulling manifest", "indeterminate remote progress") assert.NotContains(t, got, "%", "indeterminate remote progress") - peer.update(progressPush("engine:remote-progress", + peer.update(progressPush(t, "engine:remote-progress", map[string]any{"node": "peer", "engine": "ollama", "op": "pull", "stage": "downloading", "percent": 25})) assert.Contains(t, peer.status.render(), "downloading (25%)", "reported percent is kept") local := localDetail() - local.update(progressPush("engine:pull-progress", + local.update(progressPush(t, "engine:pull-progress", map[string]any{"engine": "ollama", "stage": "pulling manifest"})) assert.NotContains(t, local.status.render(), "%", "indeterminate pull progress") - local.update(progressPush("engine:install-progress", + local.update(progressPush(t, "engine:install-progress", map[string]any{"engine": "ollama", "stage": "failed", "percent": -1, "error": "disk full"})) got = local.status.render() assert.NotContains(t, got, "%", "failed install") @@ -1076,13 +1082,13 @@ func TestOpenDetail_FollowsPresenceAndMembership(t *testing.T) { // The peer leaves the cluster but is still on the network. peer.Trusted = false - v.Update(discoveryPush(peer)) + v.Update(discoveryPush(t, peer)) assert.NotEqual(t, membershipMember, d.node.membership, "the detail still reads Member after the peer left the cluster") assert.Empty(t, d.engines, "engine controls are still offered for a node no longer in the cluster") assert.Contains(t, d.View(), "not in this cluster", "engines pane says why the list went") // And then drops off the network entirely. - v.Update(discoveryPush()) + v.Update(discoveryPush(t)) assert.NotEqual(t, presenceOnline, d.node.presence, "the detail still reads Online after the node dropped out of every feed") } diff --git a/services/nvpair-tui/ui/nodenames_test.go b/services/nvpair-tui/ui/nodenames_test.go index f04c7786..9a83eedb 100644 --- a/services/nvpair-tui/ui/nodenames_test.go +++ b/services/nvpair-tui/ui/nodenames_test.go @@ -16,11 +16,13 @@ import ( ) // identityChanged builds the cluster:identity-changed push the manager emits. -func identityChanged(id, friendly string) *rpc.Message { - params, _ := json.Marshal(map[string]string{ +func identityChanged(t *testing.T, id, friendly string) *rpc.Message { + t.Helper() + params, err := json.Marshal(map[string]string{ "clusterId": id, "clusterFriendlyName": friendly, }) + require.NoError(t, err) return &rpc.Message{Method: "cluster:identity-changed", Params: params} } @@ -151,7 +153,7 @@ func TestClusterLabel_IsShown(t *testing.T) { // renaming on one machine is reflected without a restart. func TestClusterLabel_UpdatesFromIdentityNotification(t *testing.T) { v := newNodesView(nil) - v.Update(NotificationMsg{Msg: identityChanged("cid-1", "Lab 3 desks")}) + v.Update(NotificationMsg{Msg: identityChanged(t, "cid-1", "Lab 3 desks")}) assert.Equal(t, "Lab 3 desks", v.clusterName) assert.Equal(t, "cid-1", v.identity.ClusterID) diff --git a/services/nvpair-tui/ui/nodetelemetry_test.go b/services/nvpair-tui/ui/nodetelemetry_test.go index 35718854..a9275aa0 100644 --- a/services/nvpair-tui/ui/nodetelemetry_test.go +++ b/services/nvpair-tui/ui/nodetelemetry_test.go @@ -57,9 +57,10 @@ func TestTelemetry_StartsWhenAnAddressArrivesLate(t *testing.T) { require.Nil(t, d.telemetryCmd(), "polled a node with no address") require.False(t, d.telemetryRunning, "claims a chain is running with nothing to poll") - params, _ := json.Marshal([]availableNode{{ + params, err := json.Marshal([]availableNode{{ HostUUID: "peer", Name: "peer", IPAddress: "10.0.0.9", Port: 14318, }}) + require.NoError(t, err) cmd := d.handleNotification(&rpc.Message{ Method: "discovery:nodes-changed", Params: params, }) @@ -99,13 +100,15 @@ func TestTelemetryChains_AreGenerationScoped(t *testing.T) { // telemetry at all. func TestPollTelemetry_WalksEveryAddress(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write([]byte(`{"telemetryValid":true}`)) + _, err := w.Write([]byte(`{"telemetryValid":true}`)) + assert.NoError(t, err) })) defer srv.Close() host, port, err := net.SplitHostPort(strings.TrimPrefix(srv.URL, "http://")) require.NoError(t, err, "split test server address") - p, _ := strconv.Atoi(port) + p, err := strconv.Atoi(port) + require.NoError(t, err) // An unreachable address first — the reserved TEST-NET-1 block — then the // one that answers. @@ -139,13 +142,14 @@ func TestPollTelemetry_DecodesResponse(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, nodeInfoPath, r.URL.Path, "polled endpoint") w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{ + _, err := w.Write([]byte(`{ "GPUs":[{"name":"Test GPU","vram_bytes":8589934592,"vram_used_bytes":1073741824,"utilization_percent":42}], "cpu":{"name":"Test CPU","cores":8,"utilization_percent":13}, "memory":{"total_bytes":34359738368,"used_bytes":8589934592}, "telemetryValid":true, "msSince":120 }`)) + assert.NoError(t, err) })) defer srv.Close() @@ -189,15 +193,16 @@ func TestPollTelemetry_SkipsUnknownAddress(t *testing.T) { // name of the machine that used to hold it. func TestTelemetryFromAnotherMachine_IsRefused(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write([]byte(`{"telemetryValid":true,"hostUuid":"uuid-b"}`)) + _, err := w.Write([]byte(`{"telemetryValid":true,"hostUuid":"uuid-b"}`)) + assert.NoError(t, err) })) defer srv.Close() host, port := splitTestServer(t, srv.URL) assert.Error(t, pollTelemetryCmd("uuid-a", 1, []string{host}, port, "uuid-a")().(nodeTelemetryMsg).err, "another machine's readings were accepted for this one") - assert.NoError(t, pollTelemetryCmd("uuid-b", 1, []string{host}, port, "uuid-b")().(nodeTelemetryMsg).err, "the machine's own readings were refused") + require.NoError(t, pollTelemetryCmd("uuid-b", 1, []string{host}, port, "uuid-b")().(nodeTelemetryMsg).err, "the machine's own readings were refused") // A row with nothing to check against takes whichever machine answers. - assert.NoError(t, pollTelemetryCmd("manual:1", 1, []string{host}, port, "")().(nodeTelemetryMsg).err, "an unchecked poll was refused") + require.NoError(t, pollTelemetryCmd("manual:1", 1, []string{host}, port, "")().(nodeTelemetryMsg).err, "an unchecked poll was refused") assert.Equal(t, "uuid-a", telemetryIdentity(nodeRow{key: "uuid-a"}), "a discovered node is checked against its UUID") test := func(name string, row nodeRow) { diff --git a/services/nvpair-tui/ui/service_test.go b/services/nvpair-tui/ui/service_test.go index 6fd35234..22214520 100644 --- a/services/nvpair-tui/ui/service_test.go +++ b/services/nvpair-tui/ui/service_test.go @@ -307,7 +307,7 @@ func resetBroker(t *testing.T, result string) (*rpc.Client, <-chan string) { } else { reply.Result = json.RawMessage(`{}`) } - _ = broker.Write(reply) + assert.NoError(t, broker.Write(reply)) } }() return client, called diff --git a/services/nvpair-tui/ui/toast_test.go b/services/nvpair-tui/ui/toast_test.go index 43069d23..bbde6958 100644 --- a/services/nvpair-tui/ui/toast_test.go +++ b/services/nvpair-tui/ui/toast_test.go @@ -107,7 +107,7 @@ func TestNodesView_RetiresPinOnInviteDeclined(t *testing.T) { v.outboundInviteID = "inv-1" v.status.pin("invite sent to peer - PIN 123456") - v.Update(inviteEvent("cluster:invite-declined", "inv-1")) + v.Update(inviteEvent(t, "cluster:invite-declined", "inv-1")) assert.Empty(t, v.invitedKey, "pending invite still tracked after it was declined") rendered := v.status.render() @@ -147,16 +147,20 @@ func TestNodesView_KeepsPinWhileInvitePending(t *testing.T) { } // inviteEvent builds a terminal invite notification carrying an inviteId. -func inviteEvent(method, inviteID string) NotificationMsg { - params, _ := json.Marshal(map[string]string{"inviteId": inviteID}) +func inviteEvent(t *testing.T, method, inviteID string) NotificationMsg { + t.Helper() + params, err := json.Marshal(map[string]string{"inviteId": inviteID}) + require.NoError(t, err) return NotificationMsg{Msg: &rpc.Message{Method: method, Params: params}} } // inviteReceived is an inbound pairing request, as the cluster manager pushes it. -func inviteReceived(inviteID, from string) NotificationMsg { - params, _ := json.Marshal(map[string]string{ +func inviteReceived(t *testing.T, inviteID, from string) NotificationMsg { + t.Helper() + params, err := json.Marshal(map[string]string{ "inviteId": inviteID, "fromNodeName": from, }) + require.NoError(t, err) return NotificationMsg{Msg: &rpc.Message{ Method: "cluster:invite-received", Params: params, }} @@ -171,7 +175,7 @@ func inviteReceived(inviteID, from string) NotificationMsg { func TestInboundPairing_IsPromptedOnce(t *testing.T) { v := newNodesView(nil) v.SetSize(120, 30) - v.Update(inviteReceived("inv-1", "M2GT9CR405")) + v.Update(inviteReceived(t, "inv-1", "M2GT9CR405")) prompt := v.inboundPrompt() require.Contains(t, prompt, "M2GT9CR405", "the prompt names the machine asking") @@ -191,7 +195,7 @@ func TestInboundInvite_DoesNotHideOutboundPIN(t *testing.T) { v.Update(nodeInviteMsg{name: "peer", inviteID: "outbound", pin: "123456"}) require.Contains(t, v.View(), "PIN 123456", "outbound PIN was not visible before the inbound invite arrived") - v.Update(inviteReceived("inbound", "other peer")) + v.Update(inviteReceived(t, "inbound", "other peer")) require.Equal(t, "outbound", v.outboundInviteID, "outbound invite is no longer pending") require.NotNil(t, v.inbound, "inbound invite was not recorded") @@ -208,7 +212,7 @@ func TestInboundInvite_DoesNotHideOutboundPIN(t *testing.T) { func TestAcceptingPairing_ChangesThePrompt(t *testing.T) { v := newNodesView(nil) v.SetSize(120, 30) - v.Update(inviteReceived("inv-2", "M2GT9CR405")) + v.Update(inviteReceived(t, "inv-2", "M2GT9CR405")) v.handleKey(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(nodePairKey.Help().Key)}) require.Equal(t, nodesInputPin, v.mode, "accept did not open the PIN field") @@ -239,20 +243,20 @@ func TestInviteOutcome_MatchesBySession(t *testing.T) { assert.NotNil(t, v.inbound, "an unattributable event cleared a live pairing session") // A decline for a third, unrelated session touches neither. - v.Update(inviteEvent("cluster:invite-declined", "somebody-else")) + v.Update(inviteEvent(t, "cluster:invite-declined", "somebody-else")) assert.Equal(t, "mine", v.outboundInviteID, "an unrelated invite's decline cleared our outbound session") assert.NotEmpty(t, v.invitedKey, "an unrelated invite's decline cleared our outbound session") assert.NotNil(t, v.inbound, "an unrelated invite's decline cleared the inbound prompt") // A decline for our outbound invite clears that, and leaves the inbound // request alone. - v.Update(inviteEvent("cluster:invite-declined", "mine")) + v.Update(inviteEvent(t, "cluster:invite-declined", "mine")) assert.Empty(t, v.outboundInviteID, "our own decline did not clear the outbound session") assert.Empty(t, v.invitedKey, "our own decline did not clear the outbound session") assert.NotNil(t, v.inbound, "our outbound decline also cleared the unrelated inbound prompt") // And the inbound one resolves on its own id. - v.Update(inviteEvent("cluster:invite-expired", "theirs")) + v.Update(inviteEvent(t, "cluster:invite-expired", "theirs")) assert.Nil(t, v.inbound, "the inbound prompt survived its own expiry") } @@ -491,7 +495,7 @@ func TestRosterRead_DoesNotOverwriteANewerPush(t *testing.T) { func TestMalformedPIN_KeepsTheRequest(t *testing.T) { v := newNodesView(nil) v.SetSize(120, 30) - v.Update(inviteReceived("inv-1", "peer")) + v.Update(inviteReceived(t, "inv-1", "peer")) require.NotNil(t, v.respondToInvite(true, "12345"), "the answer was not sent") require.NotNil(t, v.inbound, "the request left the screen before it was settled") @@ -515,14 +519,14 @@ func TestMalformedPIN_KeepsTheRequest(t *testing.T) { func TestInboundInvites_ArePromptedInArrivalOrder(t *testing.T) { v := newNodesView(nil) v.SetSize(120, 30) - v.Update(inviteReceived("first", "alpha")) - v.Update(inviteReceived("second", "beta")) - v.Update(inviteReceived("second", "beta")) // delivered twice + v.Update(inviteReceived(t, "first", "alpha")) + v.Update(inviteReceived(t, "second", "beta")) + v.Update(inviteReceived(t, "second", "beta")) // delivered twice require.Equal(t, "first", v.inbound.InviteID, "the newer request replaced the one on screen") assert.Contains(t, v.inboundPrompt(), "1 more waiting", "the prompt says another request is waiting") - v.Update(inviteEvent("cluster:invite-expired", "first")) + v.Update(inviteEvent(t, "cluster:invite-expired", "first")) require.NotNil(t, v.inbound, "the waiting request did not come up next") require.Equal(t, "second", v.inbound.InviteID, "the waiting request did not come up next") assert.NotContains(t, v.inboundPrompt(), "more waiting", "a request delivered twice was queued twice") @@ -535,15 +539,15 @@ func TestInboundInvites_ArePromptedInArrivalOrder(t *testing.T) { func TestDroppedRequest_TakesItsPINWithIt(t *testing.T) { v := newNodesView(nil) v.SetSize(120, 30) - v.Update(inviteReceived("first", "alpha")) - v.Update(inviteReceived("second", "beta")) + v.Update(inviteReceived(t, "first", "alpha")) + v.Update(inviteReceived(t, "second", "beta")) v.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("a")}) v.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("1234")}) require.Equal(t, nodesInputPin, v.mode, "the PIN field did not take the digits") require.Equal(t, "1234", v.input.Value(), "the PIN field did not take the digits") - v.Update(inviteEvent("cluster:invite-expired", "first")) + v.Update(inviteEvent(t, "cluster:invite-expired", "first")) require.NotNil(t, v.inbound, "the waiting request did not come up next") require.Equal(t, "second", v.inbound.InviteID, "the waiting request did not come up next") assert.NotEqual(t, nodesInputPin, v.mode, "the PIN field outlived its request") @@ -559,10 +563,13 @@ func TestDroppedRequest_TakesItsPINWithIt(t *testing.T) { func TestJoining_DeclinesOtherPendingInvites(t *testing.T) { c1, c2 := net.Pipe() ctx, cancel := context.WithCancel(context.Background()) + brokerDone := make(chan error, 1) t.Cleanup(func() { cancel() c1.Close() c2.Close() + err := <-brokerDone + require.NoError(t, err, "mock broker") }) client := rpc.NewClient(c1, c1) go client.Run(ctx) @@ -575,25 +582,34 @@ func TestJoining_DeclinesOtherPendingInvites(t *testing.T) { answers := make(chan answer, 8) broker := rpc.NewCodec(c2, c2) go func() { - for { - req, err := broker.Read() - if err != nil { - return + brokerDone <- func() error { + for { + req, err := broker.Read() + if err != nil { + if ctx.Err() != nil { + return nil + } + return err + } + if req.Method == "cluster:respond-to-invite" { + var a answer + if err := json.Unmarshal(req.Params, &a); err != nil { + return err + } + answers <- a + } + if err := broker.Write(&rpc.Message{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(`{}`)}); err != nil { + return err + } } - if req.Method == "cluster:respond-to-invite" { - var a answer - _ = json.Unmarshal(req.Params, &a) - answers <- a - } - _ = broker.Write(&rpc.Message{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(`{}`)}) - } + }() }() v := newNodesView(client) v.SetSize(120, 30) - v.Update(inviteReceived("a", "alpha")) - v.Update(inviteReceived("b", "beta")) - v.Update(inviteReceived("c", "gamma")) + v.Update(inviteReceived(t, "a", "alpha")) + v.Update(inviteReceived(t, "b", "beta")) + v.Update(inviteReceived(t, "c", "gamma")) cmd := v.Update(pairingResultMsg{inviteID: "a", from: "alpha", state: inviteStatePaired}) require.Nil(t, v.inbound, "requests are still waiting after joining") @@ -604,8 +620,12 @@ func TestJoining_DeclinesOtherPendingInvites(t *testing.T) { batch, ok := cmd().(tea.BatchMsg) require.True(t, ok, "joining issued no commands") + commandsDone := make(chan struct{}, len(batch)) for _, c := range batch { - go c() + go func() { + c() + commandsDone <- struct{}{} + }() } declined := map[string]bool{} deadline := time.After(2 * time.Second) @@ -623,6 +643,13 @@ func TestJoining_DeclinesOtherPendingInvites(t *testing.T) { assert.Contains(t, declined, "b", "decline the two still waiting") assert.Contains(t, declined, "c", "decline the two still waiting") assert.NotContains(t, declined, "a", "decline the two still waiting") + for range batch { + select { + case <-commandsDone: + case <-deadline: + require.FailNow(t, "decline commands did not receive their replies") + } + } } // TestJoiningWithNothingWaitingDeclinesNothing checks the ordinary case still @@ -630,7 +657,7 @@ func TestJoining_DeclinesOtherPendingInvites(t *testing.T) { func TestJoining_SendsNoDeclinesWhenNoInvitesAreWaiting(t *testing.T) { v := newNodesView(nil) v.SetSize(120, 30) - v.Update(inviteReceived("a", "alpha")) + v.Update(inviteReceived(t, "a", "alpha")) v.Update(pairingResultMsg{inviteID: "a", from: "alpha", state: inviteStatePaired}) got := v.status.render() assert.Contains(t, got, "paired with alpha", "lone accept") @@ -651,7 +678,7 @@ func TestPairing_StaysVisibleOverTheDetailScreen(t *testing.T) { v.Update(nodeInviteMsg{name: "peer", inviteID: "out", pin: "123456"}) assert.Contains(t, v.View(), "PIN 123456", "a live PIN is hidden behind the detail screen") - v.Update(inviteReceived("in", "other peer")) + v.Update(inviteReceived(t, "in", "other peer")) got := v.View() assert.Contains(t, got, "pairing request from other peer", "an inbound request is hidden behind the detail screen") assert.LessOrEqual(t, renderedRows(got), height, "pairing line stays within the frame") @@ -793,10 +820,10 @@ func TestNodesView_ClearsInboundInviteOnExpiry(t *testing.T) { accept := nodePairKey.Help().Key v := newNodesView(nil) - v.Update(inviteReceived("inv-1", "peer")) + v.Update(inviteReceived(t, "inv-1", "peer")) require.Contains(t, v.inboundPrompt(), "to accept", "no prompt after a pairing request arrived") - v.Update(inviteEvent("cluster:invite-expired", "inv-1")) + v.Update(inviteEvent(t, "cluster:invite-expired", "inv-1")) assert.Nil(t, v.inbound, "expired inbound invite is still pending; accept would target a dead invite") assert.NotContains(t, v.inboundPrompt(), "to accept", "still offering accept/decline for an expired invite") diff --git a/services/nvpair-tui/ui/updatecheck_test.go b/services/nvpair-tui/ui/updatecheck_test.go index 1f6ea57b..8d4083b0 100644 --- a/services/nvpair-tui/ui/updatecheck_test.go +++ b/services/nvpair-tui/ui/updatecheck_test.go @@ -83,7 +83,8 @@ func TestFetchLatestRelease_ReadsTheTag(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "application/vnd.github+json", r.Header.Get("Accept")) w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"tag_name":"v0.92.0","draft":false,"prerelease":false}`)) + _, err := w.Write([]byte(`{"tag_name":"v0.92.0","draft":false,"prerelease":false}`)) + assert.NoError(t, err) })) defer srv.Close() @@ -96,7 +97,8 @@ func TestFetchLatestRelease_IgnoresDraftsAndPrereleases(t *testing.T) { test := func(name, body string) { t.Run(name, func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = w.Write([]byte(body)) + _, err := w.Write([]byte(body)) + assert.NoError(t, err) })) defer srv.Close() got, err := fetchLatestRelease(srv.URL) @@ -126,7 +128,8 @@ func TestFetchLatestRelease_RefusesRedirects(t *testing.T) { var reached bool target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { reached = true - _, _ = w.Write([]byte(`{"tag_name":"v9.9.9"}`)) + _, err := w.Write([]byte(`{"tag_name":"v9.9.9"}`)) + assert.NoError(t, err) })) defer target.Close() @@ -135,7 +138,8 @@ func TestFetchLatestRelease_RefusesRedirects(t *testing.T) { })) defer srv.Close() - got, _ := fetchLatestRelease(srv.URL) + got, err := fetchLatestRelease(srv.URL) + assert.Error(t, err, "redirect must be refused") assert.False(t, reached, "the redirect was followed") assert.NotEqual(t, "9.9.9", got, "a redirected body must not be accepted") } diff --git a/services/nvpair-ui-broker/advertiser_test.go b/services/nvpair-ui-broker/advertiser_test.go index dd5b855f..4ecf4ec1 100644 --- a/services/nvpair-ui-broker/advertiser_test.go +++ b/services/nvpair-ui-broker/advertiser_test.go @@ -60,14 +60,14 @@ func TestLMStudioFallback_NeverAdvertisesItsProxy(t *testing.T) { go func() { codec := NewCodec(proxyServer) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var got proxyLocalBackend - if json.Unmarshal(msg.Params, &got) == nil { + if assert.NoError(t, json.Unmarshal(msg.Params, &got)) { localBackend <- got } - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) }() b := &Broker{regCache: relay.NewRegistrationCache()} @@ -166,8 +166,8 @@ func attachAdvertiserProxy( t.Helper() proxyClient, proxyServer := net.Pipe() t.Cleanup(func() { - _ = proxyClient.Close() - _ = proxyServer.Close() + assert.NoError(t, proxyClient.Close()) + assert.NoError(t, proxyServer.Close()) }) proxy := &proxyProcess{ peer: NewPeer(NewCodec(proxyClient)), @@ -181,14 +181,14 @@ func attachAdvertiserProxy( codec := NewCodec(proxyServer) for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var update proxyLocalBackend - if json.Unmarshal(msg.Params, &update) == nil { + if assert.NoError(t, json.Unmarshal(msg.Params, &update)) { updates <- update } - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) } }() return updates diff --git a/services/nvpair-ui-broker/broker_lifecycle_test.go b/services/nvpair-ui-broker/broker_lifecycle_test.go index cc29555b..efd64968 100644 --- a/services/nvpair-ui-broker/broker_lifecycle_test.go +++ b/services/nvpair-ui-broker/broker_lifecycle_test.go @@ -139,11 +139,11 @@ func TestInferenceShutdown_StopsTheProxyBeforeEngines(t *testing.T) { go func() { codec := NewCodec(engineServer) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } order <- "engine-manager" - _ = codec.Respond(msg.ID, nil) + assert.NoError(t, codec.Respond(msg.ID, nil)) }() b := &Broker{proxySup: proxySup} @@ -222,10 +222,10 @@ func TestUnbindableLMStudioFacade_FinishesWithoutRestartingTheProcess(t *testing codec := NewCodec(proxyServer) for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } - _ = codec.Respond(msg.ID, proxyReadyParams{Port: 1}) + assert.NoError(t, codec.Respond(msg.ID, proxyReadyParams{Port: 1})) } }() diff --git a/services/nvpair-ui-broker/brokertest_test.go b/services/nvpair-ui-broker/brokertest_test.go index 858d813d..0bf24e60 100644 --- a/services/nvpair-ui-broker/brokertest_test.go +++ b/services/nvpair-ui-broker/brokertest_test.go @@ -13,10 +13,13 @@ package main import ( "encoding/json" + "errors" + "io" "net" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -114,7 +117,7 @@ func brokerWithEngineInventory(t *testing.T, ports map[string]int) *Broker { go func() { codec := NewCodec(server) request, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } switch request.Method { @@ -122,19 +125,19 @@ func brokerWithEngineInventory(t *testing.T, ports map[string]int) *Broker { var params struct { Engine string `json:"engine"` } - if json.Unmarshal(request.Params, ¶ms) != nil { - _ = codec.RespondError(request.ID, -32602, "invalid engine status request") + if !assert.NoError(t, json.Unmarshal(request.Params, ¶ms)) { + assert.NoError(t, codec.RespondError(request.ID, -32602, "invalid engine status request")) return } - _ = codec.Respond(request.ID, map[string]any{"port": ports[params.Engine]}) + assert.NoError(t, codec.Respond(request.ID, map[string]any{"port": ports[params.Engine]})) case "engine:get-installed": engines := make([]map[string]any, 0, len(ports)) for engine, port := range ports { engines = append(engines, map[string]any{"engine": engine, "port": port}) } - _ = codec.Respond(request.ID, map[string]any{"engines": engines}) + assert.NoError(t, codec.Respond(request.ID, map[string]any{"engines": engines})) default: - _ = codec.RespondError(request.ID, -32602, "unexpected engine status request") + assert.NoError(t, codec.RespondError(request.ID, -32602, "unexpected engine status request")) } }() t.Cleanup(func() { @@ -144,6 +147,15 @@ func brokerWithEngineInventory(t *testing.T, ports map[string]int) *Broker { return b } +// assertRPCRead records malformed frames and accepts normal fixture shutdown. +func assertRPCRead(t *testing.T, err error) bool { + t.Helper() + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, net.ErrClosed) { + return false + } + return assert.NoError(t, err) +} + // isolateOllamaHostTestConfig points the per-user config dir at a temp dir so a // test's persisted state cannot touch the developer's real configuration. // diff --git a/services/nvpair-ui-broker/enginefacade_test.go b/services/nvpair-ui-broker/enginefacade_test.go index c0feae2f..e1ba9745 100644 --- a/services/nvpair-ui-broker/enginefacade_test.go +++ b/services/nvpair-ui-broker/enginefacade_test.go @@ -49,8 +49,8 @@ func TestDefaultEngineFacade_RetriesAwayFromReservedPorts(t *testing.T) { proxyClient, proxyServer := net.Pipe() t.Cleanup(func() { - _ = proxyClient.Close() - _ = proxyServer.Close() + assert.NoError(t, proxyClient.Close()) + assert.NoError(t, proxyServer.Close()) }) proxy := &proxyProcess{peer: NewPeer(NewCodec(proxyClient))} go proxy.peer.Serve(nil, nil) @@ -98,7 +98,7 @@ func TestLlamaCPPFacadePreparation_PreservesConfiguredPorts(t *testing.T) { go func() { for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } calls.Add(1) @@ -157,8 +157,8 @@ func TestLlamaCPPEngineStatus_RelaysBeforeOtherPortGates(t *testing.T) { t.Cleanup(func() { close(b.ollamaPortReady) close(b.lmstudioPortReady) - _ = client.Close() - _ = server.Close() + assert.NoError(t, client.Close()) + assert.NoError(t, server.Close()) }) b.codec = NewCodec(server) id := json.RawMessage(`1`) @@ -178,8 +178,8 @@ func TestLlamaCPPProxyNotificationDispatch_PreservesFacadeAddress(t *testing.T) profile := mustEngineProxyProfile("llamacpp") client, server := net.Pipe() t.Cleanup(func() { - _ = client.Close() - _ = server.Close() + assert.NoError(t, client.Close()) + assert.NoError(t, server.Close()) }) b := &Broker{codec: NewCodec(server)} b.proxyMu.Lock() diff --git a/services/nvpair-ui-broker/engineproxy_test.go b/services/nvpair-ui-broker/engineproxy_test.go index 15b02039..78c37237 100644 --- a/services/nvpair-ui-broker/engineproxy_test.go +++ b/services/nvpair-ui-broker/engineproxy_test.go @@ -168,22 +168,22 @@ func TestDeselectedEngine_IsNotPrepared(t *testing.T) { go func() { for { msg, err := settingsCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } if msg.Method == "settings/get-force-ports" { policyReads <- struct{}{} } - _ = settingsCodec.Respond(msg.ID, map[string]bool{"value": true}) + assert.NoError(t, settingsCodec.Respond(msg.ID, map[string]bool{"value": true})) } }() go func() { for { msg, err := engineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } - _ = engineCodec.Respond(msg.ID, json.RawMessage(`{}`)) + assert.NoError(t, engineCodec.Respond(msg.ID, json.RawMessage(`{}`))) } }() diff --git a/services/nvpair-ui-broker/enginesettings_security_test.go b/services/nvpair-ui-broker/enginesettings_security_test.go index 0bdd4ff2..83fd44e0 100644 --- a/services/nvpair-ui-broker/enginesettings_security_test.go +++ b/services/nvpair-ui-broker/enginesettings_security_test.go @@ -23,7 +23,8 @@ func TestSettings_DerivesCORSGuardFromAuthenticatedCaller(t *testing.T) { h := newSettingsHarness(t) pub, key, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err) - uri, _ := url.Parse("urn:nvpair:node:paired-caller") + uri, err := url.Parse("urn:nvpair:node:paired-caller") + require.NoError(t, err) cert := &x509.Certificate{SerialNumber: big.NewInt(1), URIs: []*url.URL{uri}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(time.Hour)} der, err := x509.CreateCertificate(rand.Reader, cert, cert, pub, key) require.NoError(t, err) diff --git a/services/nvpair-ui-broker/enginesettings_test.go b/services/nvpair-ui-broker/enginesettings_test.go index 4bb0c250..e276c579 100644 --- a/services/nvpair-ui-broker/enginesettings_test.go +++ b/services/nvpair-ui-broker/enginesettings_test.go @@ -74,7 +74,7 @@ func newSettingsHarnessForEngine(t *testing.T, engine string) *settingsHarness { go func() { for { msg, err := proxyCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } if !msg.IsRequest() { @@ -82,7 +82,7 @@ func newSettingsHarnessForEngine(t *testing.T, engine string) *settingsHarness { } engine, method := engines.SplitAddressedMethod(msg.Method) if engine == "" || method != "set-port" { - _ = proxyCodec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, proxyCodec.Respond(msg.ID, map[string]bool{"ok": true})) continue } var p struct { @@ -95,14 +95,14 @@ func newSettingsHarnessForEngine(t *testing.T, engine string) *settingsHarness { proxy.readyMu.Lock() proxy.facadeState[engine] = proxyFacadeState{ready: true, port: p.Port} proxy.readyMu.Unlock() - _ = proxyCodec.Respond(msg.ID, map[string]int{"port": p.Port}) + assert.NoError(t, proxyCodec.Respond(msg.ID, map[string]int{"port": p.Port})) } }() launch := settings.LaunchState{Engine: engine, ServerPort: ports[0], EffectivePort: ports[0], LaunchText: "--fixture-option", Running: true, Editable: true, Format: "pair-arguments-v1"} go func() { for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } if !msg.IsRequest() { @@ -113,12 +113,12 @@ func newSettingsHarnessForEngine(t *testing.T, engine string) *settingsHarness { h.launchMu.Lock() current := launch h.launchMu.Unlock() - _ = codec.Respond(msg.ID, current) + assert.NoError(t, codec.Respond(msg.ID, current)) case "engine:configured-ports": - _ = codec.Respond(msg.ID, map[string]any{"engines": []any{map[string]any{"engine": "lmstudio", "port": h.otherEnginePort.Load()}}}) + assert.NoError(t, codec.Respond(msg.ID, map[string]any{"engines": []any{map[string]any{"engine": "lmstudio", "port": h.otherEnginePort.Load()}}})) case "engine:preview-launch": var p settings.Request - _ = json.Unmarshal(msg.Params, &p) + assert.NoError(t, json.Unmarshal(msg.Params, &p)) h.previewPreserveCORS.Store(p.PreserveCORS) if p.Format == "pair-launch-v1" { p.Settings.LaunchText = strings.TrimPrefix(p.Settings.LaunchText, "managed serve ") @@ -126,7 +126,7 @@ func newSettingsHarnessForEngine(t *testing.T, engine string) *settingsHarness { h.launchMu.Lock() restart := launch.Running && (p.Settings.ServerPort != launch.ServerPort || p.Settings.LaunchText != launch.LaunchText) h.launchMu.Unlock() - _ = codec.Respond(msg.ID, settings.Preview{Settings: p.Settings, Restart: restart}) + assert.NoError(t, codec.Respond(msg.ID, settings.Preview{Settings: p.Settings, Restart: restart})) case "engine:configure-launch": // Serve this off the reader loop: a suspended apply must not // stop the fixture from answering an unrelated read, which is @@ -145,22 +145,22 @@ func newSettingsHarnessForEngine(t *testing.T, engine string) *settingsHarness { proxy.facadeState[engine] = state proxy.readyMu.Unlock() } - _ = codec.RespondError(msg.ID, -32000, "stop failure") + assert.NoError(t, codec.RespondError(msg.ID, -32000, "stop failure")) return } var p settings.Configure - _ = json.Unmarshal(msg.Params, &p) + assert.NoError(t, json.Unmarshal(msg.Params, &p)) h.launchMu.Lock() launch.ServerPort = p.Settings.ServerPort launch.LaunchText = p.Settings.LaunchText launch.Running = false h.launchMu.Unlock() if err := h.b.rebindSettingsProxy(p.Engine, p.Settings.ProxyPort); err != nil { - _ = codec.RespondError(msg.ID, -32000, err.Error()) + assert.NoError(t, codec.RespondError(msg.ID, -32000, err.Error())) return } if h.fail.Load() { - _ = codec.RespondError(msg.ID, -32000, "startup failure") + assert.NoError(t, codec.RespondError(msg.ID, -32000, "startup failure")) return } h.launchMu.Lock() @@ -169,14 +169,15 @@ func newSettingsHarnessForEngine(t *testing.T, engine string) *settingsHarness { current := launch h.launchMu.Unlock() if h.failResultSave.Load() { - journal, _ := h.b.engineSettingsPath() + journal, pathErr := h.b.engineSettingsPath() + assert.NoError(t, pathErr) assert.NoError(t, os.Remove(journal)) assert.NoError(t, os.Mkdir(journal, 0700)) } - _ = codec.Respond(msg.ID, current) + assert.NoError(t, codec.Respond(msg.ID, current)) }(msg) default: - _ = codec.RespondError(msg.ID, -32601, "unsupported fixture method") + assert.NoError(t, codec.RespondError(msg.ID, -32601, "unsupported fixture method")) } } }() @@ -329,7 +330,8 @@ func TestSettingsJournal_ReportsPersistenceFailureAndRecoversInterruptedApply(t h := newSettingsHarness(t) p := h.request(t) p.Settings.LaunchText += " --new" - path, _ := h.b.engineSettingsPath() + path, pathErr := h.b.engineSettingsPath() + require.NoError(t, pathErr) require.NoError(t, os.Remove(path)) require.NoError(t, os.Mkdir(path, 0700)) _, err := h.b.applyEngineSettings(context.Background(), p, "") @@ -383,7 +385,8 @@ func TestSettingsPortValidation_IncludesStoppedEnginesAndAliases(t *testing.T) { func TestSettings_MigratesLegacyProxyChoiceBeforeManagedDefaults(t *testing.T) { h := newSettingsHarness(t) - path, _ := h.b.engineSettingsPath() + path, pathErr := h.b.engineSettingsPath() + require.NoError(t, pathErr) require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(path), "proxy-port.json"), []byte(`{"port":26080}`), 0600)) h.b.migrateLegacyEngineSettings() config, ok := h.b.explicitEngineSettings("ollama") diff --git a/services/nvpair-ui-broker/health_connections_test.go b/services/nvpair-ui-broker/health_connections_test.go index c671cb37..55bb7d16 100644 --- a/services/nvpair-ui-broker/health_connections_test.go +++ b/services/nvpair-ui-broker/health_connections_test.go @@ -29,9 +29,10 @@ func TestHealthChecks_ReuseConnections(t *testing.T) { } w.WriteHeader(status) if chunked { - _ = http.NewResponseController(w).Flush() + assert.NoError(t, http.NewResponseController(w).Flush()) } - _, _ = io.WriteString(w, `{"models":[],"status":"responding"}`) + _, writeErr := io.WriteString(w, `{"models":[],"status":"responding"}`) + assert.NoError(t, writeErr) })) const rounds = 32 for i := 0; i < rounds; i++ { diff --git a/services/nvpair-ui-broker/lmstudioport_test.go b/services/nvpair-ui-broker/lmstudioport_test.go index d53a47fe..c286154d 100644 --- a/services/nvpair-ui-broker/lmstudioport_test.go +++ b/services/nvpair-ui-broker/lmstudioport_test.go @@ -102,25 +102,25 @@ func TestManagedLMStudioReady_OpensGateAndPushesBackend(t *testing.T) { go func() { codec := NewCodec(engineServer) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } engineMethod <- msg.Method - _ = codec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: managedLMStudioBackendStart}) + assert.NoError(t, codec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: managedLMStudioBackendStart})) }() backend := make(chan proxyLocalBackend, 1) go func() { codec := NewCodec(proxyServer) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var got proxyLocalBackend - if json.Unmarshal(msg.Params, &got) == nil { + if assert.NoError(t, json.Unmarshal(msg.Params, &got)) { backend <- got } - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) }() b.forwardLMStudioProxyNotification("ready", json.RawMessage(`{"version":"test","port":1234}`)) @@ -135,8 +135,8 @@ func TestManagedLMStudioReady_OpensGateAndPushesBackend(t *testing.T) { select { case got := <-backend: require.Equal(t, "lmstudio", got.Engine, "local backend (%v, %v)", got, managedLMStudioBackendStart) - require.Equal(t, managedLMStudioBackendStart, got.Port, "local backend (%v, %v)", got, managedLMStudioBackendStart) - require.True(t, got.Healthy, "local backend (%v, %v)", got, managedLMStudioBackendStart) + require.Equal(t, managedLMStudioBackendStart, got.Port, "local backend") + require.True(t, got.Healthy, "local backend must be healthy") case <-time.After(2 * time.Second): require.FailNow(t, "LM Studio ready did not push the local backend") } @@ -167,23 +167,23 @@ func TestLMStudioReady_FallsBackAndWarnsForUnexpectedPort(t *testing.T) { setPortCount := 0 for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } if msg.Method == lmstudioSetLocalBackend { - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) return } var p struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &p) + assert.NoError(t, json.Unmarshal(msg.Params, &p)) requestedPorts <- p.Port setPortCount++ if setPortCount == 1 { - _ = codec.RespondError(msg.ID, -32000, "occupied") + assert.NoError(t, codec.RespondError(msg.ID, -32000, "occupied")) } else { - _ = codec.Respond(msg.ID, proxyReadyParams{Port: p.Port}) + assert.NoError(t, codec.Respond(msg.ID, proxyReadyParams{Port: p.Port})) } } }() @@ -195,11 +195,11 @@ func TestLMStudioReady_FallsBackAndWarnsForUnexpectedPort(t *testing.T) { warnings := make(chan warning, 1) go func() { msg, err := NewCodec(errorsServer).Read() - if err != nil { + if !assertRPCRead(t, err) { return } var got warning - if json.Unmarshal(msg.Params, &got) == nil { + if assert.NoError(t, json.Unmarshal(msg.Params, &got)) { warnings <- got } }() @@ -255,11 +255,11 @@ func TestLMStudioRequests_WaitForPortGate(t *testing.T) { go func() { codec := NewCodec(engineServer) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } method <- msg.Method - _ = codec.Respond(msg.ID, map[string]any{}) + assert.NoError(t, codec.Respond(msg.ID, map[string]any{})) }() response := make(chan error, 1) go func() { @@ -362,15 +362,15 @@ func TestManagedLMStudioConcurrentReady_WaitsForFallbackRebind(t *testing.T) { count := 0 for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } count++ requests <- msg.Method if msg.Method == lmstudioSetPort && count == 1 { - _ = codec.RespondError(msg.ID, -32000, "occupied") + assert.NoError(t, codec.RespondError(msg.ID, -32000, "occupied")) } else { - _ = codec.Respond(msg.ID, proxyReadyParams{Port: managedLMStudioBackendStart + 1}) + assert.NoError(t, codec.Respond(msg.ID, proxyReadyParams{Port: managedLMStudioBackendStart + 1})) } } }() @@ -432,11 +432,11 @@ func TestLMStudioFallback_FinishesOnlyFailedFacadeWhenPortsAreExhausted(t *testi codec := NewCodec(proxyServer) for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } attempts <- msg.Method - _ = codec.RespondError(msg.ID, -32000, "occupied") + assert.NoError(t, codec.RespondError(msg.ID, -32000, "occupied")) } }() @@ -464,37 +464,38 @@ func TestPrepareManagedLMStudioFacade_MovesDefaultBackend(t *testing.T) { calls := make(chan string, 3) go func() { msg, err := settingsCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } calls <- msg.Method - _ = settingsCodec.Respond(msg.ID, map[string]bool{"value": true}) + assert.NoError(t, settingsCodec.Respond(msg.ID, map[string]bool{"value": true})) }() go func() { + type enginePortRequest struct { + Engine string `json:"engine"` + Port int `json:"port"` + } status, err := engineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } calls <- status.Method - var statusRequest struct { - Engine string `json:"engine"` - Port int `json:"port"` + var statusRequest enginePortRequest + if assert.NoError(t, json.Unmarshal(status.Params, &statusRequest)) { + assert.Equal(t, enginePortRequest{"lmstudio", managedLMStudioFacadePort}, statusRequest, "engine:status params") } - assert.False(t, json.Unmarshal(status.Params, &statusRequest) != nil || - statusRequest.Engine != "lmstudio" || statusRequest.Port != managedLMStudioFacadePort, "engine:status params") - _ = engineCodec.Respond(status.ID, ollamaPortStatus{Running: true, Port: managedLMStudioFacadePort}) + assert.NoError(t, engineCodec.Respond(status.ID, ollamaPortStatus{Running: true, Port: managedLMStudioFacadePort})) setPort, err := engineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } calls <- setPort.Method - var request struct { - Engine string `json:"engine"` - Port int `json:"port"` + var request enginePortRequest + if assert.NoError(t, json.Unmarshal(setPort.Params, &request)) { + assert.Equal(t, enginePortRequest{"lmstudio", managedLMStudioBackendStart}, request, "engine:set-port params") } - assert.False(t, json.Unmarshal(setPort.Params, &request) != nil || request.Engine != "lmstudio" || request.Port != managedLMStudioBackendStart, "engine:set-port params") - _ = engineCodec.Respond(setPort.ID, ollamaPortStatus{Running: true, Port: managedLMStudioBackendStart}) + assert.NoError(t, engineCodec.Respond(setPort.ID, ollamaPortStatus{Running: true, Port: managedLMStudioBackendStart})) }() b.prepareManagedLMStudioFacadeWithPortCheck(func(port int) bool { @@ -525,13 +526,13 @@ func TestPrepareUnmanagedLMStudio_PreservesPortsAndWaitsForProxy(t *testing.T) { go func() { msg, err := settingsCodec.Read() if err == nil { - _ = settingsCodec.Respond(msg.ID, map[string]bool{"value": false}) + assert.NoError(t, settingsCodec.Respond(msg.ID, map[string]bool{"value": false})) } }() go func() { msg, err := engineCodec.Read() if err == nil { - _ = engineCodec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: 12400}) + assert.NoError(t, engineCodec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: 12400})) } }() @@ -572,7 +573,7 @@ func TestManagedLMStudioBindFailure_WaitsForFallbackReady(t *testing.T) { codec := NewCodec(proxyServer) msg, err := codec.Read() if err == nil { - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) } }() @@ -621,7 +622,7 @@ func TestUnmanagedLMStudioProxyCollision_RebindsBeforeGate(t *testing.T) { codec := NewCodec(proxyServer) for i := 0; i < 2; i++ { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } methods <- msg.Method @@ -630,16 +631,16 @@ func TestUnmanagedLMStudioProxyCollision_RebindsBeforeGate(t *testing.T) { var request struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &request) + assert.NoError(t, json.Unmarshal(msg.Params, &request)) requestedFallback <- request.Port - _ = codec.Respond(msg.ID, proxyReadyParams{Port: request.Port}) + assert.NoError(t, codec.Respond(msg.ID, proxyReadyParams{Port: request.Port})) case lmstudioSetLocalBackend: var backend proxyLocalBackend - _ = json.Unmarshal(msg.Params, &backend) + assert.NoError(t, json.Unmarshal(msg.Params, &backend)) localBackend <- backend - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) default: - _ = codec.RespondError(msg.ID, -32601, "unexpected method") + assert.NoError(t, codec.RespondError(msg.ID, -32601, "unexpected method")) } } }() @@ -684,7 +685,7 @@ func TestUnmanagedLMStudioCollision_RebindsBeforeStatusProbe(t *testing.T) { codec := NewCodec(proxyServer) for i := 0; i < 2; i++ { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } order <- msg.Method @@ -692,20 +693,20 @@ func TestUnmanagedLMStudioCollision_RebindsBeforeStatusProbe(t *testing.T) { var request struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &request) - _ = codec.Respond(msg.ID, proxyReadyParams{Port: request.Port}) + assert.NoError(t, json.Unmarshal(msg.Params, &request)) + assert.NoError(t, codec.Respond(msg.ID, proxyReadyParams{Port: request.Port})) } else { - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) } } }() go func() { msg, err := engineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } order <- msg.Method - _ = engineCodec.Respond(msg.ID, ollamaPortStatus{Port: 12400}) + assert.NoError(t, engineCodec.Respond(msg.ID, ollamaPortStatus{Port: 12400})) }() b.forwardLMStudioProxyNotification("ready", json.RawMessage(`{"port":12400}`)) @@ -741,7 +742,7 @@ func TestUnknownLMStudioBackend_MovesProxyBeforeStatusProbe(t *testing.T) { codec := NewCodec(proxyServer) for i := 0; i < 2; i++ { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } order <- msg.Method @@ -749,21 +750,21 @@ func TestUnknownLMStudioBackend_MovesProxyBeforeStatusProbe(t *testing.T) { var request struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &request) + assert.NoError(t, json.Unmarshal(msg.Params, &request)) fallbackPort <- request.Port - _ = codec.Respond(msg.ID, proxyReadyParams{Port: request.Port}) + assert.NoError(t, codec.Respond(msg.ID, proxyReadyParams{Port: request.Port})) } else { - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) } } }() go func() { msg, err := engineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } order <- msg.Method - _ = engineCodec.Respond(msg.ID, ollamaPortStatus{Port: managedLMStudioBackendStart}) + assert.NoError(t, engineCodec.Respond(msg.ID, ollamaPortStatus{Port: managedLMStudioBackendStart})) }() b.forwardLMStudioProxyNotification("ready", json.RawMessage(`{"port":1235}`)) @@ -792,11 +793,11 @@ func TestUnknownCustomBackend_KeepsRestoreGatedAfterStatusFailure(t *testing.T) go func() { for i := 0; i < 2; i++ { msg, err := engineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } statusCalls <- struct{}{} - _ = engineCodec.RespondError(msg.ID, -32000, "status unavailable") + assert.NoError(t, engineCodec.RespondError(msg.ID, -32000, "status unavailable")) } }() // Let the pre-fix local-backend call finish so an incorrect gate close is @@ -805,10 +806,10 @@ func TestUnknownCustomBackend_KeepsRestoreGatedAfterStatusFailure(t *testing.T) codec := NewCodec(proxyServer) for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) } }() @@ -850,7 +851,7 @@ func TestEngineManagerRespawn_ReconcilesUnknownCustomBackendBeforeRestore(t *tes msg, err := oldEngineCodec.Read() if err == nil { oldStatus <- struct{}{} - _ = oldEngineCodec.RespondError(msg.ID, -32000, "manager exiting") + assert.NoError(t, oldEngineCodec.RespondError(msg.ID, -32000, "manager exiting")) } }() restoreDone := make(chan bool, 1) @@ -869,11 +870,11 @@ func TestEngineManagerRespawn_ReconcilesUnknownCustomBackendBeforeRestore(t *tes order := make(chan string, 4) go func() { status, err := newEngineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } order <- status.Method - _ = newEngineCodec.Respond(status.ID, ollamaPortStatus{Port: 12400}) + assert.NoError(t, newEngineCodec.Respond(status.ID, ollamaPortStatus{Port: 12400})) restore, err := newEngineCodec.Read() if err == nil { @@ -884,7 +885,7 @@ func TestEngineManagerRespawn_ReconcilesUnknownCustomBackendBeforeRestore(t *tes codec := NewCodec(proxyServer) for i := 0; i < 2; i++ { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } order <- msg.Method @@ -892,10 +893,10 @@ func TestEngineManagerRespawn_ReconcilesUnknownCustomBackendBeforeRestore(t *tes var request struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &request) - _ = codec.Respond(msg.ID, proxyReadyParams{Port: request.Port}) + assert.NoError(t, json.Unmarshal(msg.Params, &request)) + assert.NoError(t, codec.Respond(msg.ID, proxyReadyParams{Port: request.Port})) } else { - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) } } }() diff --git a/services/nvpair-ui-broker/nodeactivity_test.go b/services/nvpair-ui-broker/nodeactivity_test.go index 297dfe90..f06a76e2 100644 --- a/services/nvpair-ui-broker/nodeactivity_test.go +++ b/services/nvpair-ui-broker/nodeactivity_test.go @@ -56,7 +56,7 @@ func relayHarness(t *testing.T) (*Broker, <-chan *Message) { go func() { codec := NewCodec(scannerSide) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } // Deliberately does NOT respond: a liveness report is a notification, so @@ -204,7 +204,7 @@ func TestActivity_IsNotForwardedToClients(t *testing.T) { go func() { codec := NewCodec(uiSide) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } toClient <- msg diff --git a/services/nvpair-ui-broker/nodeinfo_observed_test.go b/services/nvpair-ui-broker/nodeinfo_observed_test.go index 1e8bea32..36eb35c3 100644 --- a/services/nvpair-ui-broker/nodeinfo_observed_test.go +++ b/services/nvpair-ui-broker/nodeinfo_observed_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/noderec" @@ -38,11 +39,11 @@ func TestForwardNodeInfoObservedAddresses_ReachesTheScanner(t *testing.T) { go func() { codec := NewCodec(scannerSide) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } + assert.NoError(t, codec.Respond(msg.ID, map[string]bool{"ok": true})) relayed <- msg - _ = codec.Respond(msg.ID, map[string]bool{"ok": true}) }() b.forwardNodeInfoNotification(noderec.NotifyObservedAddresses, json.RawMessage(from)) diff --git a/services/nvpair-ui-broker/ollamahost_test.go b/services/nvpair-ui-broker/ollamahost_test.go index 52f23bba..5bf7bcd9 100644 --- a/services/nvpair-ui-broker/ollamahost_test.go +++ b/services/nvpair-ui-broker/ollamahost_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/appdir" @@ -213,7 +214,8 @@ func TestAliasWarning_ReplaysAfterErrorsProcessRecovery(t *testing.T) { received := make(chan *Message, 1) go func() { - msg, _ := NewCodec(server).Read() + msg, err := NewCodec(server).Read() + assert.NoError(t, err) received <- msg }() b.replayOllamaHostAliasError() @@ -283,16 +285,16 @@ func TestDisableAlias_ClearsEngineReservation(t *testing.T) { go func() { codec := NewCodec(server) request, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var params struct { Port int `json:"port"` } - if request.Method == "internal:set-reserved-port" && json.Unmarshal(request.Params, ¶ms) == nil { + if request.Method == "internal:set-reserved-port" && assert.NoError(t, json.Unmarshal(request.Params, ¶ms)) { reserved <- params.Port } - _ = codec.Respond(request.ID, map[string]int{"port": params.Port}) + assert.NoError(t, codec.Respond(request.ID, map[string]int{"port": params.Port})) }() b.disableOllamaHostAliasReservation() @@ -319,40 +321,40 @@ func TestManagedAliasReservationSync_UsesReplacementEngineManager(t *testing.T) oldReservation := make(chan int, 1) go func() { msg, err := oldEngineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var request struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &request) + assert.NoError(t, json.Unmarshal(msg.Params, &request)) oldReservation <- request.Port - _ = oldEngineCodec.Respond(msg.ID, map[string]int{"port": request.Port}) + assert.NoError(t, oldEngineCodec.Respond(msg.ID, map[string]int{"port": request.Port})) }() replacementReservation := make(chan int, 1) go func() { for range 3 { msg, err := replacementCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } switch msg.Method { case "engine:status": - _ = replacementCodec.Respond(msg.ID, ollamaPortStatus{Port: 16000}) + assert.NoError(t, replacementCodec.Respond(msg.ID, ollamaPortStatus{Port: 16000})) case "engine:get-installed": - _ = replacementCodec.Respond(msg.ID, map[string]any{ + assert.NoError(t, replacementCodec.Respond(msg.ID, map[string]any{ "engines": []map[string]any{{"engine": "ollama", "port": 16000}}, - }) + })) case "internal:set-reserved-port": var request struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &request) + assert.NoError(t, json.Unmarshal(msg.Params, &request)) replacementReservation <- request.Port - _ = replacementCodec.Respond(msg.ID, map[string]int{"port": request.Port}) + assert.NoError(t, replacementCodec.Respond(msg.ID, map[string]int{"port": request.Port})) default: - _ = replacementCodec.RespondError(msg.ID, -32601, "unexpected method") + assert.NoError(t, replacementCodec.RespondError(msg.ID, -32601, "unexpected method")) } } }() @@ -471,15 +473,15 @@ func TestAliasBindFailure_ReleasesReservationButKeepsWarning(t *testing.T) { reservation := make(chan int, 1) go func() { msg, err := engineCodec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var request struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, &request) + assert.NoError(t, json.Unmarshal(msg.Params, &request)) reservation <- request.Port - _ = engineCodec.Respond(msg.ID, map[string]int{"port": request.Port}) + assert.NoError(t, engineCodec.Respond(msg.ID, map[string]int{"port": request.Port})) }() params, err := json.Marshal(errors.ServiceError{ diff --git a/services/nvpair-ui-broker/proxyaddressing_test.go b/services/nvpair-ui-broker/proxyaddressing_test.go index f5efafa2..436b40b7 100644 --- a/services/nvpair-ui-broker/proxyaddressing_test.go +++ b/services/nvpair-ui-broker/proxyaddressing_test.go @@ -51,11 +51,11 @@ func TestRelay_AddressesForwardedMethodToRequestedFacade(t *testing.T) { go func() { codec := NewCodec(server) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } seen <- msg.Method - _ = codec.Respond(msg.ID, map[string][]string{"nodes": {}}) + assert.NoError(t, codec.Respond(msg.ID, map[string][]string{"nodes": {}})) }() b := &Broker{codec: NewCodec(rwDiscard{})} diff --git a/services/nvpair-ui-broker/proxyport_test.go b/services/nvpair-ui-broker/proxyport_test.go index 652f0beb..74cd8c58 100644 --- a/services/nvpair-ui-broker/proxyport_test.go +++ b/services/nvpair-ui-broker/proxyport_test.go @@ -31,7 +31,7 @@ func TestEnabledEngineRestore_WaitsForBothPortGates(t *testing.T) { var msg struct { Method string `json:"method"` } - if json.NewDecoder(server).Decode(&msg) == nil { + if assert.NoError(t, json.NewDecoder(server).Decode(&msg)) { method <- msg.Method } }() @@ -141,8 +141,8 @@ func TestOwningOllamaReady_OpensGateAfterMove(t *testing.T) { go func() { codec := NewCodec(engineServer) msg, err := codec.Read() - if err == nil { - _ = codec.Respond(msg.ID, ollamaPortStatus{Port: managedOllamaBackendStart + 1}) + if assert.NoError(t, err) { + assert.NoError(t, codec.Respond(msg.ID, ollamaPortStatus{Port: managedOllamaBackendStart + 1})) } }() @@ -233,11 +233,11 @@ func TestLMStudioSetPort_UpdatesBackendCache(t *testing.T) { go func() { codec := NewCodec(engineServer) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } request <- msg - _ = codec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: 12400}) + assert.NoError(t, codec.Respond(msg.ID, ollamaPortStatus{Running: true, Port: 12400})) }() response := make(chan *Message, 1) go func() { @@ -372,12 +372,13 @@ func TestRelay_CachesActualOllamaPortFromResponse(t *testing.T) { codec := NewCodec(engineServer) request, err := codec.Read() if err == nil { - _ = codec.Respond(request.ID, map[string]any{"engine": "ollama", "port": 11435}) + assert.NoError(t, codec.Respond(request.ID, map[string]any{"engine": "ollama", "port": 11435})) } }() response := make(chan *Message, 1) go func() { - msg, _ := NewCodec(clientConn).Read() + msg, err := NewCodec(clientConn).Read() + assert.NoError(t, err) response <- msg }() @@ -407,7 +408,8 @@ func TestBroker_DoesNotExposeInternalReservationSetter(t *testing.T) { id := json.RawMessage(`10`) response := make(chan *Message, 1) go func() { - msg, _ := NewCodec(clientConn).Read() + msg, err := NewCodec(clientConn).Read() + assert.NoError(t, err) response <- msg }() diff --git a/services/nvpair-ui-broker/proxyportbump_test.go b/services/nvpair-ui-broker/proxyportbump_test.go index 1b5f4e94..d1ec4740 100644 --- a/services/nvpair-ui-broker/proxyportbump_test.go +++ b/services/nvpair-ui-broker/proxyportbump_test.go @@ -36,14 +36,14 @@ func brokerWithRunningEngines(t *testing.T, ports ...int) *Broker { go func() { codec := NewCodec(server) request, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } engines := make([]map[string]any, 0, len(ports)) for _, port := range ports { engines = append(engines, map[string]any{"running": true, "port": port}) } - _ = codec.Respond(request.ID, map[string]any{"engines": engines}) + assert.NoError(t, codec.Respond(request.ID, map[string]any{"engines": engines})) }() t.Cleanup(func() { _ = client.Close() @@ -68,7 +68,7 @@ func observeErrors(t *testing.T, b *Broker) <-chan *Message { codec := NewCodec(server) for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } select { @@ -115,18 +115,18 @@ func TestReconcileUnmanagedProxyPort_BumpsOffRunningEngine(t *testing.T) { go func() { codec := NewCodec(proxyServer) msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var params struct { Port int `json:"port"` } - _ = json.Unmarshal(msg.Params, ¶ms) + assert.NoError(t, json.Unmarshal(msg.Params, ¶ms)) select { case setPort <- params.Port: default: } - _ = codec.Respond(msg.ID, map[string]any{"port": params.Port}) + assert.NoError(t, codec.Respond(msg.ID, map[string]any{"port": params.Port})) }() b.reconcileProxyPortOnReady(11435) @@ -212,23 +212,23 @@ func serveFacadeEnable(t *testing.T, conn net.Conn, reject map[int]bool, attempt codec := NewCodec(conn) for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } if msg.Method != "facade/enable" { continue } var spec enableFacadeRequest - if err := json.Unmarshal(msg.Params, &spec); err != nil { + if !assert.NoError(t, json.Unmarshal(msg.Params, &spec)) { return } attempts <- spec if reject[spec.Port] { - _ = codec.RespondError(msg.ID, codeFacadeBindFailed, - fmt.Sprintf("facade bind failed: port %d: address already in use", spec.Port)) + assert.NoError(t, codec.RespondError(msg.ID, codeFacadeBindFailed, + fmt.Sprintf("facade bind failed: port %d: address already in use", spec.Port))) continue } - _ = codec.Respond(msg.ID, enableFacadeReply{Engine: spec.Engine, Port: spec.Port}) + assert.NoError(t, codec.Respond(msg.ID, enableFacadeReply{Engine: spec.Engine, Port: spec.Port})) } }() } @@ -294,16 +294,16 @@ func TestFacadeEnableRejection_IsNotRetried(t *testing.T) { codec := NewCodec(proxyServer) for { msg, err := codec.Read() - if err != nil { + if !assertRPCRead(t, err) { return } var spec enableFacadeRequest - if json.Unmarshal(msg.Params, &spec) != nil { + if !assert.NoError(t, json.Unmarshal(msg.Params, &spec)) { return } attempts <- spec // -32602 is what an unknown engine or an unsupported alias returns. - _ = codec.RespondError(msg.ID, -32602, "unknown engine \"nope\"") + assert.NoError(t, codec.RespondError(msg.ID, -32602, "unknown engine \"nope\"")) } }() diff --git a/services/nvpair-ui-broker/scheduler_test.go b/services/nvpair-ui-broker/scheduler_test.go index dbdb0faa..0c0754bd 100644 --- a/services/nvpair-ui-broker/scheduler_test.go +++ b/services/nvpair-ui-broker/scheduler_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/noderec" @@ -31,8 +32,8 @@ func TestSchedulerFeedBaseline_PrecedesConcurrentLiveWorkload(t *testing.T) { }) worker := &rpcWorker{peer: NewPeer(NewCodec(brokerSide))} b := &Broker{workloads: workloadstore.New(), telemetry: newTelemetryCache()} - b.workloads.Apply(storeIncoming("active", "host", "ollama", "run", "running", "a")) - b.workloads.Apply(storeIncoming("historic", "host", "ollama", "run", "completed", "a")) + b.workloads.Apply(storeIncoming(t, "active", "host", "ollama", "run", "running", "a")) + b.workloads.Apply(storeIncoming(t, "historic", "host", "ollama", "run", "completed", "a")) b.telemetry.Upsert(sourceScanner, noderec.NodeTelemetry{ HostUUID: "a", GPUUtilizationPct: 50, @@ -50,14 +51,14 @@ func TestSchedulerFeedBaseline_PrecedesConcurrentLiveWorkload(t *testing.T) { close(feedLocked) replayed := b.replayActiveWorkloadsToScheduler(worker) b.replayTelemetryToScheduler(worker) - _ = worker.Notify("discovery:nodes-changed", []AvailableNode{{HostUUID: "a"}}) + assert.NoError(t, worker.Notify("discovery:nodes-changed", []AvailableNode{{HostUUID: "a"}})) b.schedulerFeedMu.Unlock() b.workloadEmitMu.Unlock() initDone <- replayed }() <-feedLocked - liveInfo := storeIncoming("live", "host", "lmstudio", "run", "queued", "a").Info + liveInfo := storeIncoming(t, "live", "host", "lmstudio", "run", "queued", "a").Info liveParams, err := json.Marshal(map[string]json.RawMessage{"workloadInfo": liveInfo}) require.NoError(t, err, "marshal live workload") liveDone := make(chan struct{}) diff --git a/services/nvpair-ui-broker/settingsport_test.go b/services/nvpair-ui-broker/settingsport_test.go index dd942cf0..c5643187 100644 --- a/services/nvpair-ui-broker/settingsport_test.go +++ b/services/nvpair-ui-broker/settingsport_test.go @@ -20,7 +20,10 @@ import ( func callBrokerPortRequest(t *testing.T, b *Broker, method string, params json.RawMessage) *Message { t.Helper() client, server := net.Pipe() - t.Cleanup(func() { _ = client.Close(); _ = server.Close() }) + t.Cleanup(func() { + assert.NoError(t, client.Close()) + assert.NoError(t, server.Close()) + }) require.NoError(t, client.SetReadDeadline(time.Now().Add(5*time.Second)), "set response deadline") b.codec = NewCodec(server) id := json.RawMessage(`1`) @@ -108,7 +111,7 @@ func TestBrokerLlamaCPPProxySetPort_RejectsConflicts(t *testing.T) { test("occupied listener", func(t *testing.T, h *settingsHarness, before settings.Snapshot) int { ln, err := net.Listen("tcp", ":0") require.NoError(t, err, "bind occupied port") - t.Cleanup(func() { _ = ln.Close() }) + t.Cleanup(func() { assert.NoError(t, ln.Close()) }) return ln.Addr().(*net.TCPAddr).Port }) } diff --git a/services/nvpair-ui-broker/staleworkload_test.go b/services/nvpair-ui-broker/staleworkload_test.go index 8315619b..ad202045 100644 --- a/services/nvpair-ui-broker/staleworkload_test.go +++ b/services/nvpair-ui-broker/staleworkload_test.go @@ -23,13 +23,13 @@ func TestFailStaleForeignWorkloads_RetiresSilentRemoteWork(t *testing.T) { b := &Broker{workloads: workloadstore.New(), nodeID: "self"} // Remote-origin, executing on the origin: the sweep's business. - require.True(t, b.workloads.Apply(storeIncoming("7", "peer-uuid", "ollama", "r1", "running", "peer-uuid")), "remote running apply should be accepted") + require.True(t, b.workloads.Apply(storeIncoming(t, "7", "peer-uuid", "ollama", "r1", "running", "peer-uuid")), "remote running apply should be accepted") // Local-origin: our own proxies are the authority and never re-assert here. - require.True(t, b.workloads.Apply(storeIncoming("8", "self", "ollama", "r1", "running", "self")), "local running apply should be accepted") + require.True(t, b.workloads.Apply(storeIncoming(t, "8", "self", "ollama", "r1", "running", "self")), "local running apply should be accepted") // Remote-origin but executing HERE. Still the sweep's business: lifecycle // events come from the origin's proxy, not from our engine, so nothing else // will ever clear this record. - require.True(t, b.workloads.Apply(storeIncoming("9", "peer-uuid", "ollama", "r1", "running", "self")), "remote-origin local-executor apply should be accepted") + require.True(t, b.workloads.Apply(storeIncoming(t, "9", "peer-uuid", "ollama", "r1", "running", "self")), "remote-origin local-executor apply should be accepted") // Nothing is stale while the origin is still within its silence budget. b.failStaleForeignWorkloads(time.Hour) @@ -55,19 +55,19 @@ func TestFailStaleForeignWorkloads_RetiresSilentRemoteWork(t *testing.T) { func TestFailStaleForeignWorkloads_YieldsToTheOrigin(t *testing.T) { b := &Broker{workloads: workloadstore.New(), nodeID: "self"} // Executing on the origin, so the sweep is entitled to judge it. - b.workloads.Apply(storeIncoming("9", "peer-uuid", "ollama", "r1", "running", "peer-uuid")) + b.workloads.Apply(storeIncoming(t, "9", "peer-uuid", "ollama", "r1", "running", "peer-uuid")) b.failStaleForeignWorkloads(-time.Second) r, _ := b.workloads.Get("peer-uuid", "9") require.Equal(t, "failed", r.State) - require.True(t, b.workloads.Apply(storeIncoming("9", "peer-uuid", "ollama", "r1", "running", "peer-uuid")), "the origin's authoritative running must override the inferred failure") + require.True(t, b.workloads.Apply(storeIncoming(t, "9", "peer-uuid", "ollama", "r1", "running", "peer-uuid")), "the origin's authoritative running must override the inferred failure") r, _ = b.workloads.Get("peer-uuid", "9") require.Equal(t, "running", r.State, "record (%v)", r) require.False(t, r.Inferred, "record (%v)", r) // And the origin's real terminal still lands afterwards. - require.True(t, b.workloads.Apply(storeIncoming("9", "peer-uuid", "ollama", "r1", "completed", "peer-uuid")), "the origin's terminal must apply") + require.True(t, b.workloads.Apply(storeIncoming(t, "9", "peer-uuid", "ollama", "r1", "completed", "peer-uuid")), "the origin's terminal must apply") r, _ = b.workloads.Get("peer-uuid", "9") require.Equal(t, "completed", r.State, "record (%v)", r) require.False(t, r.Inferred, "record (%v)", r) diff --git a/services/nvpair-ui-broker/workloadmanager_test.go b/services/nvpair-ui-broker/workloadmanager_test.go index 48aac858..5b17f7fb 100644 --- a/services/nvpair-ui-broker/workloadmanager_test.go +++ b/services/nvpair-ui-broker/workloadmanager_test.go @@ -16,12 +16,14 @@ import ( "nvpair-ui-broker/workloadstore" ) -func storeIncoming(id, origin, engine, runID, state, scheduledOn string) workloadstore.Incoming { +func storeIncoming(t *testing.T, id, origin, engine, runID, state, scheduledOn string) workloadstore.Incoming { + t.Helper() m := map[string]any{ "id": id, "originatedFrom": origin, "engine": engine, "runId": runID, "state": state, "scheduledOn": scheduledOn, "createdAt": 1, "model": "m", } - b, _ := json.Marshal(m) + b, err := json.Marshal(m) + require.NoError(t, err) in, _ := workloadstore.ParseIncoming(b) return in } @@ -35,11 +37,11 @@ func storeIncoming(id, origin, engine, runID, state, scheduledOn string) workloa func TestActiveLocalReplay_EmitsFramesForLocalActiveWorkloads(t *testing.T) { b := &Broker{workloads: workloadstore.New(), nodeID: "host"} - b.workloads.Apply(storeIncoming("1", "host", "ollama", "r1", "running", "peer")) // local-origin active → replay - b.workloads.Apply(storeIncoming("2", "host", "ollama", "r1", "failed", "peer")) // local-origin recent terminal → replay - b.workloads.Apply(storeIncoming("3", "peer", "ollama", "r2", "running", "host")) // peer-origin (scheduled here) → skip - b.workloads.Apply(storeIncoming("4", "host", "lmstudio", "r3", "queued", "host")) // local-origin active (other engine) → replay - b.workloads.Apply(storeIncoming("5", "host", "ollama", "r4", "completed", "host")) // local-origin recent terminal → replay + b.workloads.Apply(storeIncoming(t, "1", "host", "ollama", "r1", "running", "peer")) // local-origin active → replay + b.workloads.Apply(storeIncoming(t, "2", "host", "ollama", "r1", "failed", "peer")) // local-origin recent terminal → replay + b.workloads.Apply(storeIncoming(t, "3", "peer", "ollama", "r2", "running", "host")) // peer-origin (scheduled here) → skip + b.workloads.Apply(storeIncoming(t, "4", "host", "lmstudio", "r3", "queued", "host")) // local-origin active (other engine) → replay + b.workloads.Apply(storeIncoming(t, "5", "host", "ollama", "r4", "completed", "host")) // local-origin recent terminal → replay frames := b.activeLocalReplayFrames() require.Len(t, frames, 4) @@ -75,7 +77,7 @@ func TestWorkloadHistoryFlusher_FlushesOnShutdown(t *testing.T) { // A terminal completes. With the default 5 s periodic flush and an immediate // shutdown, only the flusher's shutdown (join) flush can have persisted it. - require.True(t, b.workloads.Apply(storeIncoming("1", "host", "ollama", "r1", "completed", "host")), "terminal apply should be accepted") + require.True(t, b.workloads.Apply(storeIncoming(t, "1", "host", "ollama", "r1", "completed", "host")), "terminal apply should be accepted") stop() // cancels + joins the flusher; its final flush must have completed // Restart: a fresh store loading the same file must see the terminal — @@ -105,7 +107,7 @@ func TestWorkloadHistoryFlusher_OutlivesParentCancel(t *testing.T) { time.Sleep(150 * time.Millisecond) // give a (hypothetically coupled) flusher time to exit // A producer emits a terminal during teardown — after parent cancel, before stop(). - require.True(t, b.workloads.Apply(storeIncoming("1", "host", "ollama", "r1", "failed", "host")), "terminal apply should be accepted") + require.True(t, b.workloads.Apply(storeIncoming(t, "1", "host", "ollama", "r1", "failed", "host")), "terminal apply should be accepted") stop() // now cancel + join the flusher; its final flush must include the terminal s2 := workloadstore.New().WithPersistence(path) @@ -121,7 +123,7 @@ func TestWorkloadHistoryFlusher_OutlivesParentCancel(t *testing.T) { func TestFailWorkloadsForNode_MatchesByHostUUID(t *testing.T) { b := &Broker{workloads: workloadstore.New(), nodeID: "self"} // A peer-origin running workload stamped with the peer's HostUUID. - require.True(t, b.workloads.Apply(storeIncoming("7", "peer-uuid", "ollama", "r1", "running", "peer-uuid")), "running apply should be accepted") + require.True(t, b.workloads.Apply(storeIncoming(t, "7", "peer-uuid", "ollama", "r1", "running", "peer-uuid")), "running apply should be accepted") // Sweeping by the display name must NOT match (it's not the workload's key). b.failWorkloadsForNode("peer-friendly-name", "peer-friendly-name") diff --git a/services/nvpair-ui-broker/workloadstore/persistence_test.go b/services/nvpair-ui-broker/workloadstore/persistence_test.go index 3f02f65f..3672bef9 100644 --- a/services/nvpair-ui-broker/workloadstore/persistence_test.go +++ b/services/nvpair-ui-broker/workloadstore/persistence_test.go @@ -29,7 +29,8 @@ func newStoreAt(path string, now int64) *Store { // mkTerm builds a terminal (failed) Incoming with a completedAt, via // ParseIncoming so Info and the projected fields stay consistent. -func mkTerm(id, origin string, createdAt, completedAt int64) Incoming { +func mkTerm(t *testing.T, id, origin string, createdAt, completedAt int64) Incoming { + t.Helper() m := map[string]any{ "id": id, "originatedFrom": origin, @@ -40,7 +41,8 @@ func mkTerm(id, origin string, createdAt, completedAt int64) Incoming { "model": "granite-embedding:latest", "engine": "ollama", } - b, _ := json.Marshal(m) + b, err := json.Marshal(m) + require.NoError(t, err) in, _ := ParseIncoming(b) return in } @@ -48,9 +50,9 @@ func mkTerm(id, origin string, createdAt, completedAt int64) Incoming { func TestWorkloadPersistence_RoundTripsOnlyTerminalHistory(t *testing.T) { path := filepath.Join(t.TempDir(), "wl.json") s := newStoreAt(path, testNow) - s.Apply(mkTerm("1", "a", testNow-1000, testNow-150)) - s.Apply(mkTerm("2", "b", testNow-1000, testNow-160)) - s.Apply(mkIn("3", "c", "running", "c", testNow-1000)) // active — must not persist + s.Apply(mkTerm(t, "1", "a", testNow-1000, testNow-150)) + s.Apply(mkTerm(t, "2", "b", testNow-1000, testNow-160)) + s.Apply(mkIn(t, "3", "c", "running", "c", testNow-1000)) // active — must not persist require.NoError(t, s.Flush(), "flush") s2 := newStoreAt(path, testNow) @@ -67,14 +69,14 @@ func TestFlush_CoalescesOnDirty(t *testing.T) { s := newStoreAt(path, testNow) // A running-only store isn't dirty → flush writes nothing. - s.Apply(mkIn("1", "a", "running", "a", testNow-1000)) + s.Apply(mkIn(t, "1", "a", "running", "a", testNow-1000)) require.False(t, s.dirty, "non-terminal apply must not mark dirty") require.NoError(t, s.Flush(), "flush") _, err := os.Stat(path) require.ErrorIs(t, err, os.ErrNotExist, "no file should be written before any terminal record") // A terminal transition marks dirty and flush writes it. - s.Apply(mkTerm("1", "a", testNow-1000, testNow-150)) + s.Apply(mkTerm(t, "1", "a", testNow-1000, testNow-150)) require.True(t, s.dirty, "terminal apply should mark dirty") require.NoError(t, s.Flush(), "flush") require.False(t, s.dirty, "dirty should be cleared after a successful flush") @@ -86,9 +88,9 @@ func TestWorkloadHistory_EvictsOldestRecordsAboveCountLimit(t *testing.T) { s := newStoreAt(path, testNow) s.historyCap = 2 - s.Apply(mkTerm("1", "a", testNow-1000, testNow-300)) - s.Apply(mkTerm("2", "a", testNow-1000, testNow-200)) - s.Apply(mkTerm("3", "a", testNow-1000, testNow-100)) + s.Apply(mkTerm(t, "1", "a", testNow-1000, testNow-300)) + s.Apply(mkTerm(t, "2", "a", testNow-1000, testNow-200)) + s.Apply(mkTerm(t, "3", "a", testNow-1000, testNow-100)) // prune runs during flush require.NoError(t, s.Flush(), "flush") require.Equal(t, 2, s.Len()) @@ -104,8 +106,8 @@ func TestWorkloadHistory_EvictsRecordsBeyondAgeLimit(t *testing.T) { s.maxAgeMs = 1000 s.now = func() time.Time { return time.UnixMilli(10000) } - s.Apply(mkTerm("old", "a", 0, 8000)) // age 2000 > 1000 → evicted - s.Apply(mkTerm("new", "a", 0, 9500)) // age 500 → kept + s.Apply(mkTerm(t, "old", "a", 0, 8000)) // age 2000 > 1000 → evicted + s.Apply(mkTerm(t, "new", "a", 0, 9500)) // age 500 → kept require.NoError(t, s.Flush(), "flush") _, ok := s.Get("a", "old") require.False(t, ok, "record older than maxAge should be pruned") @@ -116,9 +118,9 @@ func TestWorkloadHistory_EvictsRecordsBeyondAgeLimit(t *testing.T) { func TestCheckpoint_Rotates(t *testing.T) { path := filepath.Join(t.TempDir(), "wl.json") s := newStoreAt(path, testNow) - s.Apply(mkTerm("1", "a", testNow-1000, testNow-100)) + s.Apply(mkTerm(t, "1", "a", testNow-1000, testNow-100)) require.NoError(t, s.Flush(), "flush") - s.Apply(mkTerm("2", "a", testNow-1000, testNow-50)) + s.Apply(mkTerm(t, "2", "a", testNow-1000, testNow-50)) require.NoError(t, s.Checkpoint(), "checkpoint") require.FileExists(t, path, "primary should exist after checkpoint") require.FileExists(t, path+".1", "rotation .1 should exist after checkpoint") @@ -127,10 +129,10 @@ func TestCheckpoint_Rotates(t *testing.T) { func TestLoad_FallsBackOnCorruptPrimary(t *testing.T) { path := filepath.Join(t.TempDir(), "wl.json") s := newStoreAt(path, testNow) - s.Apply(mkTerm("1", "a", testNow-1000, testNow-100)) + s.Apply(mkTerm(t, "1", "a", testNow-1000, testNow-100)) // primary = {1} require.NoError(t, s.Flush(), "flush") - s.Apply(mkTerm("2", "a", testNow-1000, testNow-50)) + s.Apply(mkTerm(t, "2", "a", testNow-1000, testNow-50)) // primary = {1,2}, .1 = {1} require.NoError(t, s.Checkpoint(), "checkpoint") require.NoError(t, os.WriteFile(path, []byte("not json"), 0o600), "corrupt primary") @@ -150,8 +152,8 @@ func TestWorkloadHistoryLoad_StartsEmptyWhenFileIsMissing(t *testing.T) { func TestWorkloadPersistence_ExcludesInferredTerminalRecords(t *testing.T) { path := filepath.Join(t.TempDir(), "wl.json") s := newStoreAt(path, testNow) - s.Apply(mkTerm("2", "a", testNow-1000, testNow-100)) // authoritative terminal → persisted - s.ApplyInferred(mkTerm("1", "a", testNow-1000, testNow-100)) // inferred terminal → not persisted + s.Apply(mkTerm(t, "2", "a", testNow-1000, testNow-100)) // authoritative terminal → persisted + s.ApplyInferred(mkTerm(t, "1", "a", testNow-1000, testNow-100)) // inferred terminal → not persisted require.NoError(t, s.Flush(), "flush") s2 := newStoreAt(path, testNow) @@ -164,7 +166,7 @@ func TestWorkloadPersistence_ExcludesInferredTerminalRecords(t *testing.T) { func TestWorkloadPersistence_PerformsNoIOWhenDisabled(t *testing.T) { s := New() // no path - s.Apply(mkTerm("1", "a", testNow-1000, testNow-100)) + s.Apply(mkTerm(t, "1", "a", testNow-1000, testNow-100)) require.NoError(t, s.Flush(), "flush with persistence off should be a no-op") require.NoError(t, s.Load(), "load with persistence off should be a no-op") } diff --git a/services/nvpair-ui-broker/workloadstore/staleness_test.go b/services/nvpair-ui-broker/workloadstore/staleness_test.go index 409bc23f..db7e6e79 100644 --- a/services/nvpair-ui-broker/workloadstore/staleness_test.go +++ b/services/nvpair-ui-broker/workloadstore/staleness_test.go @@ -36,7 +36,7 @@ func TestLastSeen_CarriesAMonotonicReading(t *testing.T) { require.False(t, hasMonotonic(time.UnixMilli(1_700_000_000_000)), "detector is wrong: a time.UnixMilli value carries no monotonic reading") s := New() - require.True(t, s.Apply(mkIn("1", "peer", "running", "peer", 100)), "first running must be a forward change") + require.True(t, s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)), "first running must be a forward change") r, ok := s.Get("peer", "1") require.True(t, ok, "record missing") require.True(t, hasMonotonic(r.LastSeen), "LastSeen has no monotonic reading; staleness would be vulnerable to a wall-clock step") @@ -56,13 +56,13 @@ func hasMonotonic(t time.Time) bool { return t != t.Round(0) } func TestLastSeen_TracksOriginReassertion(t *testing.T) { s, now := clockStore(t) - require.True(t, s.Apply(mkIn("1", "peer", "running", "peer", 100)), "first running must be a forward change") + require.True(t, s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)), "first running must be a forward change") r, _ := s.Get("peer", "1") require.WithinDuration(t, *now, r.LastSeen, 0, "LastSeen") firstUpdated := r.LastUpdated *now = now.Add(30 * time.Second) - require.False(t, s.Apply(mkIn("1", "peer", "running", "peer", 100)), "an unchanged re-assertion must not report a forward change") + require.False(t, s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)), "an unchanged re-assertion must not report a forward change") r, _ = s.Get("peer", "1") require.WithinDuration(t, *now, r.LastSeen, 0, "LastSeen after re-assertion") require.True(t, r.LastUpdated == firstUpdated, "LastUpdated must NOT move on a no-op merge; that is why LastSeen exists") @@ -74,21 +74,21 @@ func TestLastSeen_TracksOriginReassertion(t *testing.T) { // evidence of anything. func TestLastSeen_IgnoresInferredAndStale(t *testing.T) { s, now := clockStore(t) - s.Apply(mkIn("1", "peer", "running", "peer", 200)) + s.Apply(mkIn(t, "1", "peer", "running", "peer", 200)) first := *now *now = now.Add(time.Minute) - s.ApplyInferred(mkIn("1", "peer", "failed", "peer", 200)) + s.ApplyInferred(mkIn(t, "1", "peer", "failed", "peer", 200)) r, _ := s.Get("peer", "1") require.WithinDuration(t, first, r.LastSeen, 0, "an inferred guess must not refresh LastSeen") // A stale generation for a live record is rejected outright and must not // refresh the sighting either. s2, now2 := clockStore(t) - s2.Apply(mkIn("2", "peer", "running", "peer", 500)) + s2.Apply(mkIn(t, "2", "peer", "running", "peer", 500)) seen := *now2 *now2 = now2.Add(time.Minute) - require.False(t, s2.Apply(mkIn("2", "peer", "running", "peer", 100)), "a stale generation must be rejected") + require.False(t, s2.Apply(mkIn(t, "2", "peer", "running", "peer", 100)), "a stale generation must be rejected") r2, _ := s2.Get("peer", "2") require.WithinDuration(t, seen, r2.LastSeen, 0, "a stale-generation event must not refresh LastSeen") } @@ -102,20 +102,20 @@ func TestStaleForeign_SelectsOnlySilentRemoteWork(t *testing.T) { s, now := clockStore(t) const ttl = 5 * time.Minute - s.Apply(mkIn("silent", "peer", "running", "peer", 100)) - s.Apply(mkIn("done", "peer", "completed", "peer", 100)) - s.Apply(mkIn("mine", "self", "running", "self", 100)) + s.Apply(mkIn(t, "silent", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "done", "peer", "completed", "peer", 100)) + s.Apply(mkIn(t, "mine", "self", "running", "self", 100)) *now = now.Add(ttl + time.Second) // The origin is still asserting this one, right now. - s.Apply(mkIn("fresh", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "fresh", "peer", "running", "peer", 100)) stale := s.StaleForeign("self", ttl) require.Len(t, stale, 1, "StaleForeign should return exactly [silent]") require.Equal(t, "silent", stale[0].ID, "stale id") // Once the origin speaks up again the record stops being stale. - s.Apply(mkIn("silent", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "silent", "peer", "running", "peer", 100)) require.Empty(t, s.StaleForeign("self", ttl), "origin re-assertion must clear staleness") } @@ -127,7 +127,7 @@ func TestApplyInferredUnchangedSince_GuardsTheSweepRace(t *testing.T) { s, now := clockStore(t) const ttl = 5 * time.Minute - s.Apply(mkIn("1", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)) *now = now.Add(ttl + time.Millisecond) stale := s.StaleForeign("self", ttl) require.Len(t, stale, 1, "expected 1 stale record") @@ -135,9 +135,9 @@ func TestApplyInferredUnchangedSince_GuardsTheSweepRace(t *testing.T) { // The origin speaks up after selection but before the guess is applied. *now = now.Add(10 * time.Millisecond) - s.Apply(mkIn("1", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)) - require.False(t, s.ApplyInferredUnchangedSince(mkIn("1", "peer", "failed", "peer", 100), seenAt), "a guess based on an obsolete sighting must not be applied") + require.False(t, s.ApplyInferredUnchangedSince(mkIn(t, "1", "peer", "failed", "peer", 100), seenAt), "a guess based on an obsolete sighting must not be applied") r, _ := s.Get("peer", "1") require.Equal(t, "running", r.State, "record (%v)", r) require.False(t, r.Inferred, "record (%v)", r) @@ -148,7 +148,7 @@ func TestApplyInferredUnchangedSince_GuardsTheSweepRace(t *testing.T) { *now = now.Add(ttl + time.Millisecond) stale = s.StaleForeign("self", ttl) require.Len(t, stale, 1, "expected the record to be stale again") - require.True(t, s.ApplyInferredUnchangedSince(mkIn("1", "peer", "failed", "peer", 100), stale[0].LastSeen), "a guess based on the current sighting must apply") + require.True(t, s.ApplyInferredUnchangedSince(mkIn(t, "1", "peer", "failed", "peer", 100), stale[0].LastSeen), "a guess based on the current sighting must apply") r, _ = s.Get("peer", "1") require.Equal(t, "failed", r.State, "record (%v)", r) require.True(t, r.Inferred, "record (%v)", r) @@ -162,14 +162,14 @@ func TestApplyInferredUnchangedSince_DoesNotRestoreRemovedWorkload(t *testing.T) s, now := clockStore(t) const ttl = 5 * time.Minute - s.Apply(mkIn("1", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)) *now = now.Add(ttl + time.Millisecond) stale := s.StaleForeign("self", ttl) require.Len(t, stale, 1, "expected 1 stale record") // The workload is retired (a peer removal) before the guess lands. require.True(t, s.Remove("peer", "1"), "remove should report a deletion") - require.False(t, s.ApplyInferredUnchangedSince(mkIn("1", "peer", "failed", "peer", 100), stale[0].LastSeen), "a guess about a removed workload must not be applied") + require.False(t, s.ApplyInferredUnchangedSince(mkIn(t, "1", "peer", "failed", "peer", 100), stale[0].LastSeen), "a guess about a removed workload must not be applied") _, ok := s.Get("peer", "1") require.False(t, ok, "the removed workload was resurrected as a synthesized terminal") } @@ -186,9 +186,9 @@ func TestStaleForeign_SweepsWorkThisNodeIsExecuting(t *testing.T) { s, now := clockStore(t) const ttl = 5 * time.Minute - s.Apply(mkIn("here", "peer", "running", "self", 100)) // routed to us - s.Apply(mkIn("there", "peer", "running", "peer", 100)) // routed elsewhere - s.Apply(mkIn("mine", "self", "running", "self", 100)) // ours: never swept + s.Apply(mkIn(t, "here", "peer", "running", "self", 100)) // routed to us + s.Apply(mkIn(t, "there", "peer", "running", "peer", 100)) // routed elsewhere + s.Apply(mkIn(t, "mine", "self", "running", "self", 100)) // ours: never swept *now = now.Add(ttl + time.Millisecond) got := map[string]bool{} @@ -213,10 +213,10 @@ func TestStaleForeign_SkipsCollidingClientKeys(t *testing.T) { // Same origin and id, different engines: two distinct workloads to the broker, // one key to the desktop. - s.Apply(mkInFull("7", "peer", "ollama", "runA", "running", "peer", 100)) - s.Apply(mkInFull("7", "peer", "lmstudio", "runB", "running", "peer", 100)) + s.Apply(mkInFull(t, "7", "peer", "ollama", "runA", "running", "peer", 100)) + s.Apply(mkInFull(t, "7", "peer", "lmstudio", "runB", "running", "peer", 100)) // An uncontested record, to prove the sweep is still working at all. - s.Apply(mkIn("solo", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "solo", "peer", "running", "peer", 100)) *now = now.Add(ttl + time.Millisecond) stale := s.StaleForeign("self", ttl) @@ -225,7 +225,7 @@ func TestStaleForeign_SkipsCollidingClientKeys(t *testing.T) { // Once the collision resolves (one generation reaches a real terminal), the // survivor becomes eligible again. - s.Apply(mkInFull("7", "peer", "lmstudio", "runB", "completed", "peer", 100)) + s.Apply(mkInFull(t, "7", "peer", "lmstudio", "runB", "completed", "peer", 100)) *now = now.Add(ttl + time.Millisecond) assert.Contains(t, replayIDSet(s.StaleForeign("self", ttl)), "7", "with only one live generation left, the silent record must be swept") } @@ -237,19 +237,19 @@ func TestStaleForeignInferredFail_IsReconcilable(t *testing.T) { s, now := clockStore(t) const ttl = 5 * time.Minute - s.Apply(mkIn("1", "peer", "running", "peer", 100)) + s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)) *now = now.Add(ttl + time.Millisecond) stale := s.StaleForeign("self", ttl) require.Len(t, stale, 1, "expected 1 stale record") - require.True(t, s.ApplyInferred(mkIn("1", "peer", "failed", "peer", 100)), "the sweep's inferred failed must apply to a live record") + require.True(t, s.ApplyInferred(mkIn(t, "1", "peer", "failed", "peer", 100)), "the sweep's inferred failed must apply to a live record") r, _ := s.Get("peer", "1") require.True(t, r.Terminal, "record (%v)", r) require.True(t, r.Inferred, "record (%v)", r) require.Empty(t, s.StaleForeign("self", ttl), "a retired record must not be swept again") // The origin comes back and insists it is still running. - require.True(t, s.Apply(mkIn("1", "peer", "running", "peer", 100)), "an authoritative running must override the inferred failed") + require.True(t, s.Apply(mkIn(t, "1", "peer", "running", "peer", 100)), "an authoritative running must override the inferred failed") r, _ = s.Get("peer", "1") require.False(t, r.Terminal, "record (%v)", r) require.False(t, r.Inferred, "record (%v)", r) diff --git a/services/nvpair-ui-broker/workloadstore/store_test.go b/services/nvpair-ui-broker/workloadstore/store_test.go index cb126680..d6fff400 100644 --- a/services/nvpair-ui-broker/workloadstore/store_test.go +++ b/services/nvpair-ui-broker/workloadstore/store_test.go @@ -13,7 +13,8 @@ import ( ) // mkIn builds an Incoming with a realistic full-fidelity Info payload. -func mkIn(id, origin, state, scheduledOn string, createdAt int64) Incoming { +func mkIn(t *testing.T, id, origin, state, scheduledOn string, createdAt int64) Incoming { + t.Helper() m := map[string]any{ "id": id, "originatedFrom": origin, @@ -23,7 +24,8 @@ func mkIn(id, origin, state, scheduledOn string, createdAt int64) Incoming { "model": "granite-embedding:latest", "engine": "ollama", } - b, _ := json.Marshal(m) + b, err := json.Marshal(m) + require.NoError(t, err) return Incoming{ Origin: origin, ID: id, @@ -39,8 +41,8 @@ func mkIn(id, origin, state, scheduledOn string, createdAt int64) Incoming { // workload. The stale running must not resurrect a finished job. func TestApply_RejectsRunningAfterTerminal(t *testing.T) { s := New() - require.True(t, s.Apply(mkIn("9", "laptop", "failed", "pc", 100)), "first sighting (failed) should be accepted") - require.False(t, s.Apply(mkIn("9", "laptop", "running", "pc", 100)), "running after failed (same generation) must be rejected") + require.True(t, s.Apply(mkIn(t, "9", "laptop", "failed", "pc", 100)), "first sighting (failed) should be accepted") + require.False(t, s.Apply(mkIn(t, "9", "laptop", "running", "pc", 100)), "running after failed (same generation) must be rejected") r, ok := s.Get("laptop", "9") require.True(t, ok, "final record (%v)", r) require.Equal(t, "failed", r.State, "final record (%v)", r) @@ -51,8 +53,8 @@ func TestApply_RejectsRunningAfterTerminal(t *testing.T) { // apply and the workload ends terminal. func TestApply_AdvancesRunningWorkloadToFailed(t *testing.T) { s := New() - require.True(t, s.Apply(mkIn("9", "laptop", "running", "pc", 100)), "running should be accepted") - require.True(t, s.Apply(mkIn("9", "laptop", "failed", "pc", 100)), "failed after running should be accepted (forward progress)") + require.True(t, s.Apply(mkIn(t, "9", "laptop", "running", "pc", 100)), "running should be accepted") + require.True(t, s.Apply(mkIn(t, "9", "laptop", "failed", "pc", 100)), "failed after running should be accepted (forward progress)") r, _ := s.Get("laptop", "9") require.Equal(t, "failed", r.State, "final record (%v)", r) require.True(t, r.Terminal, "final record (%v)", r) @@ -63,8 +65,8 @@ func TestApply_AdvancesRunningWorkloadToFailed(t *testing.T) { // replace the prior generation's terminal record, not be mistaken for it. func TestApply_ReplacesWorkloadWithNewerGeneration(t *testing.T) { s := New() - require.True(t, s.Apply(mkIn("5", "pc", "failed", "pc", 100)), "old-generation terminal should be accepted") - require.True(t, s.Apply(mkIn("5", "pc", "running", "laptop", 200)), "newer-generation running must replace the old terminal") + require.True(t, s.Apply(mkIn(t, "5", "pc", "failed", "pc", 100)), "old-generation terminal should be accepted") + require.True(t, s.Apply(mkIn(t, "5", "pc", "running", "laptop", 200)), "newer-generation running must replace the old terminal") r, _ := s.Get("pc", "5") require.Equal(t, "running", r.State, "record (%v)", r) require.Equal(t, int64(200), r.CreatedAt, "record (%v)", r) @@ -77,8 +79,8 @@ func TestApply_ReplacesWorkloadWithNewerGeneration(t *testing.T) { // one, which would otherwise look like "forward progress" by state rank. func TestApply_RejectsOlderGeneration(t *testing.T) { s := New() - require.True(t, s.Apply(mkIn("5", "pc", "running", "pc", 200)), "current-generation running should be accepted") - require.False(t, s.Apply(mkIn("5", "pc", "completed", "pc", 100)), "older-generation event must be rejected despite higher state rank") + require.True(t, s.Apply(mkIn(t, "5", "pc", "running", "pc", 200)), "current-generation running should be accepted") + require.False(t, s.Apply(mkIn(t, "5", "pc", "completed", "pc", 100)), "older-generation event must be rejected despite higher state rank") r, _ := s.Get("pc", "5") require.Equal(t, int64(200), r.CreatedAt, "record (%v)", r) require.Equal(t, "running", r.State, "record (%v)", r) @@ -88,9 +90,9 @@ func TestApply_RejectsOlderGeneration(t *testing.T) { // re-emit that re-points scheduledOn (a failover) is a meaningful change. func TestApply_ReemitsSameRankWhenAuthoritativeDataChanges(t *testing.T) { s := New() - require.True(t, s.Apply(mkIn("1", "a", "running", "node1", 100)), "initial running should be accepted") - require.False(t, s.Apply(mkIn("1", "a", "running", "node1", 100)), "identical re-emit should be a no-op") - require.True(t, s.Apply(mkIn("1", "a", "running", "node2", 100)), "running re-pointed to a new scheduledOn should be accepted") + require.True(t, s.Apply(mkIn(t, "1", "a", "running", "node1", 100)), "initial running should be accepted") + require.False(t, s.Apply(mkIn(t, "1", "a", "running", "node1", 100)), "identical re-emit should be a no-op") + require.True(t, s.Apply(mkIn(t, "1", "a", "running", "node2", 100)), "running re-pointed to a new scheduledOn should be accepted") r, _ := s.Get("a", "1") require.Equal(t, "node2", r.ScheduledOn, "scheduledOn") } @@ -103,11 +105,11 @@ func TestApply_ReemitsSameRankWhenAuthoritativeDataChanges(t *testing.T) { // toward its node's pending total and stays exposed to the staleness sweeps. func TestApplyCancelled_IsTerminal(t *testing.T) { s := New() - require.True(t, s.Apply(mkIn("1", "a", "running", "node1", 100)), "initial running should be accepted") + require.True(t, s.Apply(mkIn(t, "1", "a", "running", "node1", 100)), "initial running should be accepted") // Ranks above running, so the transition is accepted rather than dropped. - require.True(t, s.Apply(mkIn("1", "a", "cancelled", "node1", 100)), "cancelled after running should be accepted (missing from rank(): sorts below queued and is rejected)") + require.True(t, s.Apply(mkIn(t, "1", "a", "cancelled", "node1", 100)), "cancelled after running should be accepted (missing from rank(): sorts below queued and is rejected)") // Terminal, so a late running cannot resurrect it. - require.False(t, s.Apply(mkIn("1", "a", "running", "node1", 100)), "running after cancelled should be rejected as backwards") + require.False(t, s.Apply(mkIn(t, "1", "a", "running", "node1", 100)), "running after cancelled should be rejected as backwards") // Terminal, so it drops out of the active set rather than counting as load. r, ok := s.Get("a", "1") require.True(t, ok, "record should still be stored") @@ -119,8 +121,8 @@ func TestApplyCancelled_IsTerminal(t *testing.T) { // distinct workloads and must never merge against each other. func TestApply_KeepsSameWorkloadIDDistinctAcrossNodes(t *testing.T) { s := New() - require.True(t, s.Apply(mkIn("1", "A", "failed", "A", 100)), "A/1 should be accepted") - require.True(t, s.Apply(mkIn("1", "B", "running", "B", 100)), "B/1 has the same id but a different origin; must be independent") + require.True(t, s.Apply(mkIn(t, "1", "A", "failed", "A", 100)), "A/1 should be accepted") + require.True(t, s.Apply(mkIn(t, "1", "B", "running", "B", 100)), "B/1 has the same id but a different origin; must be independent") require.Equal(t, 2, s.Len()) a, _ := s.Get("A", "1") require.Equal(t, "failed", a.State, "A/1 state") @@ -137,7 +139,7 @@ func TestApply_RejectsMalformedWorkloads(t *testing.T) { func TestRemove_DeletesMatchingWorkload(t *testing.T) { s := New() - s.Apply(mkIn("1", "a", "running", "a", 100)) + s.Apply(mkIn(t, "1", "a", "running", "a", 100)) require.True(t, s.Remove("a", "1"), "Remove of present entry should report true") require.False(t, s.Remove("a", "1"), "Remove of absent entry should report false") _, ok := s.Get("a", "1") @@ -146,9 +148,9 @@ func TestRemove_DeletesMatchingWorkload(t *testing.T) { func TestSnapshot_OrdersWorkloadsByCreationTime(t *testing.T) { s := New() - s.Apply(mkIn("2", "a", "running", "a", 300)) - s.Apply(mkIn("1", "a", "running", "a", 100)) - s.Apply(mkIn("1", "b", "running", "b", 200)) + s.Apply(mkIn(t, "2", "a", "running", "a", 300)) + s.Apply(mkIn(t, "1", "a", "running", "a", 100)) + s.Apply(mkIn(t, "1", "b", "running", "b", 200)) snap := s.Snapshot() require.Len(t, snap, 3, "snapshot len") @@ -165,10 +167,10 @@ func TestSnapshot_OrdersWorkloadsByCreationTime(t *testing.T) { func TestActiveSnapshot_ExcludesTerminalHistory(t *testing.T) { s := New() - s.Apply(mkIn("completed", "a", "completed", "a", 100)) - s.Apply(mkIn("queued", "a", "queued", "b", 200)) - s.Apply(mkIn("running", "b", "running", "a", 300)) - s.Apply(mkIn("failed", "b", "failed", "b", 400)) + s.Apply(mkIn(t, "completed", "a", "completed", "a", 100)) + s.Apply(mkIn(t, "queued", "a", "queued", "b", 200)) + s.Apply(mkIn(t, "running", "b", "running", "a", 300)) + s.Apply(mkIn(t, "failed", "b", "failed", "b", 400)) snap := s.ActiveSnapshot() require.Len(t, snap, 2, "active snapshot len") @@ -188,13 +190,13 @@ func TestActiveSnapshot_ExcludesTerminalHistory(t *testing.T) { func TestActiveForNode_ExcludesTerminalAndUnrelatedWorkloads(t *testing.T) { s := New() // Non-terminal, executes on pc (origin laptop) — should match "pc". - s.Apply(mkIn("1", "laptop", "running", "pc", 100)) + s.Apply(mkIn(t, "1", "laptop", "running", "pc", 100)) // Terminal on pc — should NOT match (node-loss sweep only touches live). - s.Apply(mkIn("2", "laptop", "failed", "pc", 100)) + s.Apply(mkIn(t, "2", "laptop", "failed", "pc", 100)) // Non-terminal elsewhere — should not match "pc". - s.Apply(mkIn("3", "laptop", "running", "other", 100)) + s.Apply(mkIn(t, "3", "laptop", "running", "other", 100)) // Non-terminal originating on pc — should match "pc". - s.Apply(mkIn("4", "pc", "running", "laptop", 100)) + s.Apply(mkIn(t, "4", "pc", "running", "laptop", 100)) active := s.ActiveForNode("pc") require.Len(t, active, 2, "ActiveForNode(pc) len") @@ -216,11 +218,11 @@ func TestReplayForNode_IncludesActiveAndRecentAuthoritativeTerminalWorkloads(t * s.now = func() time.Time { return time.UnixMilli(clock) } const window = int64(60_000) - s.Apply(mkIn("1", "host", "running", "peer", 1)) // active local-origin → always - s.Apply(mkIn("2", "host", "failed", "host", 1)) // recent terminal local-origin → replay - s.Apply(mkIn("3", "peer", "running", "host", 1)) // peer-origin (scheduled here) → never - s.Apply(mkIn("4", "host", "completed", "host", 1)) // recent terminal local-origin → replay - s.ApplyInferred(mkIn("5", "host", "failed", "gone", 1)) // inferred terminal local-origin → never + s.Apply(mkIn(t, "1", "host", "running", "peer", 1)) // active local-origin → always + s.Apply(mkIn(t, "2", "host", "failed", "host", 1)) // recent terminal local-origin → replay + s.Apply(mkIn(t, "3", "peer", "running", "host", 1)) // peer-origin (scheduled here) → never + s.Apply(mkIn(t, "4", "host", "completed", "host", 1)) // recent terminal local-origin → replay + s.ApplyInferred(mkIn(t, "5", "host", "failed", "gone", 1)) // inferred terminal local-origin → never require.ElementsMatch(t, []string{"1", "2", "4"}, replayIDSet(s.ReplayForNode("host", window)), "replay active and recent terminal records, excluding peers and inferred records") @@ -242,8 +244,8 @@ func TestReplayForNodeAfterLoad_UsesCompletionTime(t *testing.T) { // Persist two terminals: one that finished ~1000s ago, one 30s ago. writer := newStoreAt(path, testNow) - writer.Apply(mkTerm("old-history", "host", testNow-1_001_000, testNow-1_000_000)) - writer.Apply(mkTerm("recent", "host", testNow-31_000, testNow-30_000)) + writer.Apply(mkTerm(t, "old-history", "host", testNow-1_001_000, testNow-1_000_000)) + writer.Apply(mkTerm(t, "recent", "host", testNow-31_000, testNow-30_000)) require.NoError(t, writer.Flush(), "flush") // A fresh store loads at the same wall clock. Load stamps LastUpdated=now on @@ -268,8 +270,8 @@ func replayIDSet(recs []Record) []string { // running job. func TestInferred_MarksRunningFailed(t *testing.T) { s := New() - s.Apply(mkIn("1", "a", "running", "a", 100)) - require.True(t, s.ApplyInferred(mkIn("1", "a", "failed", "a", 100)), "inferred failed should mark a running job failed") + s.Apply(mkIn(t, "1", "a", "running", "a", 100)) + require.True(t, s.ApplyInferred(mkIn(t, "1", "a", "failed", "a", 100)), "inferred failed should mark a running job failed") r, _ := s.Get("a", "1") require.Equal(t, "failed", r.State, "record (%v)", r) require.True(t, r.Terminal, "record (%v)", r) @@ -281,9 +283,9 @@ func TestInferred_MarksRunningFailed(t *testing.T) { // The authoritative event must win. func TestAuthoritative_OverridesInferred(t *testing.T) { s := New() - s.Apply(mkIn("1", "a", "running", "a", 100)) - s.ApplyInferred(mkIn("1", "a", "failed", "a", 100)) // node-loss guess - require.True(t, s.Apply(mkIn("1", "a", "running", "a", 100)), "authoritative running must override an inferred failed") + s.Apply(mkIn(t, "1", "a", "running", "a", 100)) + s.ApplyInferred(mkIn(t, "1", "a", "failed", "a", 100)) // node-loss guess + require.True(t, s.Apply(mkIn(t, "1", "a", "running", "a", 100)), "authoritative running must override an inferred failed") r, _ := s.Get("a", "1") require.Equal(t, "running", r.State, "record (%v)", r) require.False(t, r.Terminal, "record (%v)", r) @@ -294,8 +296,8 @@ func TestAuthoritative_OverridesInferred(t *testing.T) { // the origin's real terminal. func TestInferred_CannotOverrideAuthoritativeTerminal(t *testing.T) { s := New() - s.Apply(mkIn("1", "a", "completed", "a", 100)) - require.False(t, s.ApplyInferred(mkIn("1", "a", "failed", "a", 100)), "inferred failed must not override an authoritative terminal") + s.Apply(mkIn(t, "1", "a", "completed", "a", 100)) + require.False(t, s.ApplyInferred(mkIn(t, "1", "a", "failed", "a", 100)), "inferred failed must not override an authoritative terminal") r, _ := s.Get("a", "1") require.Equal(t, "completed", r.State, "record (%v)", r) require.False(t, r.Inferred, "record (%v)", r) @@ -305,33 +307,37 @@ func TestInferred_CannotOverrideAuthoritativeTerminal(t *testing.T) { // replaces a prior inferred one. func TestAuthoritativeTerminal_OverridesInferred(t *testing.T) { s := New() - s.Apply(mkIn("1", "a", "running", "a", 100)) - s.ApplyInferred(mkIn("1", "a", "failed", "a", 100)) - require.True(t, s.Apply(mkIn("1", "a", "completed", "a", 100)), "origin's authoritative terminal must override an inferred one") + s.Apply(mkIn(t, "1", "a", "running", "a", 100)) + s.ApplyInferred(mkIn(t, "1", "a", "failed", "a", 100)) + require.True(t, s.Apply(mkIn(t, "1", "a", "completed", "a", 100)), "origin's authoritative terminal must override an inferred one") r, _ := s.Get("a", "1") require.Equal(t, "completed", r.State, "record (%v)", r) require.False(t, r.Inferred, "record (%v)", r) } // mkInFull builds an Incoming with explicit engine + runId (and no completedAt). -func mkInFull(id, origin, engine, runID, state, scheduledOn string, createdAt int64) Incoming { +func mkInFull(t *testing.T, id, origin, engine, runID, state, scheduledOn string, createdAt int64) Incoming { + t.Helper() m := map[string]any{ "id": id, "originatedFrom": origin, "engine": engine, "runId": runID, "state": state, "scheduledOn": scheduledOn, "createdAt": createdAt, "model": "m", } - b, _ := json.Marshal(m) + b, err := json.Marshal(m) + require.NoError(t, err) in, _ := ParseIncoming(b) return in } // mkTermFull builds a terminal (failed) Incoming with explicit engine + runId. -func mkTermFull(id, origin, engine, runID string, createdAt, completedAt int64) Incoming { +func mkTermFull(t *testing.T, id, origin, engine, runID string, createdAt, completedAt int64) Incoming { + t.Helper() m := map[string]any{ "id": id, "originatedFrom": origin, "engine": engine, "runId": runID, "state": "failed", "scheduledOn": origin, "createdAt": createdAt, "completedAt": completedAt, "model": "m", } - b, _ := json.Marshal(m) + b, err := json.Marshal(m) + require.NoError(t, err) in, _ := ParseIncoming(b) return in } @@ -341,8 +347,8 @@ func mkTermFull(id, origin, engine, runID string, createdAt, completedAt int64) // and must not collide in the store. func TestWorkloadStore_KeepsConcurrentEnginesDistinct(t *testing.T) { s := New() - require.True(t, s.Apply(mkInFull("1", "host", "ollama", "ro", "running", "host", 100)), "ollama/1 should be accepted") - require.True(t, s.Apply(mkInFull("1", "host", "lmstudio", "rl", "running", "host", 200)), "lmstudio/1 shares the numeric id but a different engine; must be distinct") + require.True(t, s.Apply(mkInFull(t, "1", "host", "ollama", "ro", "running", "host", 100)), "ollama/1 should be accepted") + require.True(t, s.Apply(mkInFull(t, "1", "host", "lmstudio", "rl", "running", "host", 200)), "lmstudio/1 shares the numeric id but a different engine; must be distinct") require.Equal(t, 2, s.Len()) } @@ -350,8 +356,8 @@ func TestWorkloadStore_KeepsConcurrentEnginesDistinct(t *testing.T) { // the prior generation's terminal history must be preserved, not overwritten. func TestRestartReuse_KeepsHistory(t *testing.T) { s := New() - require.True(t, s.Apply(mkTermFull("1", "host", "ollama", "run1", 100, 150)), "gen-1 terminal should be accepted") - require.True(t, s.Apply(mkInFull("1", "host", "ollama", "run2", "running", "host", 200)), "gen-2 reused id (new run) should be a distinct workload") + require.True(t, s.Apply(mkTermFull(t, "1", "host", "ollama", "run1", 100, 150)), "gen-1 terminal should be accepted") + require.True(t, s.Apply(mkInFull(t, "1", "host", "ollama", "run2", "running", "host", 200)), "gen-2 reused id (new run) should be a distinct workload") require.Equal(t, 2, s.Len()) } @@ -361,22 +367,22 @@ func TestRestartReuse_KeepsHistory(t *testing.T) { func TestApply_ComparesGenerationBeforeProvenance(t *testing.T) { // (a) A stale authoritative event must not replace a newer inferred record. s := New() - s.Apply(mkInFull("1", "a", "ollama", "r", "running", "a", 200)) - s.ApplyInferred(mkInFull("1", "a", "ollama", "r", "failed", "a", 200)) // inferred at gen 200 - require.False(t, s.Apply(mkInFull("1", "a", "ollama", "r", "running", "a", 100)), "stale authoritative gen-100 must not replace the gen-200 record") + s.Apply(mkInFull(t, "1", "a", "ollama", "r", "running", "a", 200)) + s.ApplyInferred(mkInFull(t, "1", "a", "ollama", "r", "failed", "a", 200)) // inferred at gen 200 + require.False(t, s.Apply(mkInFull(t, "1", "a", "ollama", "r", "running", "a", 100)), "stale authoritative gen-100 must not replace the gen-200 record") r, _ := s.Get("a", "1") require.Equal(t, int64(200), r.CreatedAt) // (b) A stale inferred failure must not replace a newer authoritative running. - s.Apply(mkInFull("2", "a", "ollama", "r", "running", "a", 200)) - require.False(t, s.ApplyInferred(mkInFull("2", "a", "ollama", "r", "failed", "a", 100)), "stale inferred gen-100 must not fail a gen-200 running") + s.Apply(mkInFull(t, "2", "a", "ollama", "r", "running", "a", 200)) + require.False(t, s.ApplyInferred(mkInFull(t, "2", "a", "ollama", "r", "failed", "a", 100)), "stale inferred gen-100 must not fail a gen-200 running") r, _ = s.Get("a", "2") require.Equal(t, "running", r.State, "record (%v)", r) require.Equal(t, int64(200), r.CreatedAt, "record (%v)", r) } func TestParseIncoming_ExtractsWorkloadFields(t *testing.T) { - info := mkIn("7", "laptop", "running", "pc", 4242).Info + info := mkIn(t, "7", "laptop", "running", "pc", 4242).Info in, ok := ParseIncoming(info) require.True(t, ok, "valid workloadInfo should parse") require.Equal(t, "7", in.ID, "parsed (%v)", in) diff --git a/services/nvpair-workload-manager/cluster_mtls_test.go b/services/nvpair-workload-manager/cluster_mtls_test.go index 46d95b3d..9927bd69 100644 --- a/services/nvpair-workload-manager/cluster_mtls_test.go +++ b/services/nvpair-workload-manager/cluster_mtls_test.go @@ -36,8 +36,10 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "generate key") - serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - uri, _ := url.Parse("urn:nvpair:node:" + uuid) + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + require.NoError(t, err) + uri, err := url.Parse("urn:nvpair:node:" + uuid) + require.NoError(t, err) tmpl := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: uuid}, @@ -50,7 +52,8 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM []byte) { } der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) require.NoError(t, err, "create certificate") - keyDER, _ := x509.MarshalPKCS8PrivateKey(priv) + keyDER, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err) certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) return certPEM, keyPEM @@ -66,7 +69,8 @@ func setupNode(t *testing.T, certPEM, keyPEM []byte, pins map[string][]byte) str td := filepath.Join(dir, "trusted") require.NoError(t, os.MkdirAll(td, 0o700)) for uuid, pcert := range pins { - body, _ := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(pcert)}) + body, err := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": string(pcert)}) + require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(td, uuid+".json"), body, 0o600)) } return dir @@ -114,7 +118,7 @@ func serveEventsOverMTLS(t *testing.T, srv *Server, self, peer *clustertrust.Mes return func(body []byte) int { resp, err := client.Post(ts.URL+eventsPath, "application/json", bytes.NewReader(body)) require.NoError(t, err, "post events") - defer resp.Body.Close() + defer func() { require.NoError(t, resp.Body.Close()) }() return resp.StatusCode } } @@ -154,8 +158,10 @@ func TestWorkloadBroadcast_RequiresPinnedMutualTLS(t *testing.T) { // A valid lifecycle frame (workload:started) so a member's POST reaches 200, // not just "past the gate". wl := &Workload{ID: "wl-1", Model: "llama", Engine: "ollama", State: StateRunning, OriginatedFrom: "uuid-b", CreatedAt: 1} - params, _ := json.Marshal(lifecycleParams{WorkloadInfo: wl}) - frame, _ := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: json.RawMessage(params)}) + params, err := json.Marshal(lifecycleParams{WorkloadInfo: wl}) + require.NoError(t, err) + frame, err := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: json.RawMessage(params)}) + require.NoError(t, err) post := func(m *clustertrust.Mesh, peerUUID string) (int, error) { cfg, ok := m.ClientTLSConfig(peerUUID) @@ -170,7 +176,7 @@ func TestWorkloadBroadcast_RequiresPinnedMutualTLS(t *testing.T) { if err != nil { return 0, err } - defer resp.Body.Close() + defer func() { require.NoError(t, resp.Body.Close()) }() return resp.StatusCode, nil } @@ -215,13 +221,15 @@ func TestWorkloadEvents_RefusesUnauthenticatedRequests(t *testing.T) { defer ts.Close() wl := &Workload{ID: "wl-1", Model: "llama", Engine: "ollama", State: StateRunning, OriginatedFrom: "uuid-peer", CreatedAt: 1} - params, _ := json.Marshal(lifecycleParams{WorkloadInfo: wl}) - frame, _ := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: json.RawMessage(params)}) + params, err := json.Marshal(lifecycleParams{WorkloadInfo: wl}) + require.NoError(t, err) + frame, err := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: json.RawMessage(params)}) + require.NoError(t, err) postPlain := func() int { resp, err := http.Post(ts.URL+eventsPath, "application/json", bytes.NewReader(frame)) require.NoError(t, err, "post events") - defer resp.Body.Close() + defer func() { require.NoError(t, resp.Body.Close()) }() return resp.StatusCode } @@ -230,7 +238,8 @@ func TestWorkloadEvents_RefusesUnauthenticatedRequests(t *testing.T) { // Joining a cluster must not widen the gate either. require.NoError(t, os.WriteFile(filepath.Join(dir, "node.crt"), certPEM, 0o644)) require.NoError(t, os.WriteFile(filepath.Join(dir, "node.key"), keyPEM, 0o600)) - admission, _ := json.Marshal(map[string]any{"clusterId": "cluster-abc", "epoch": 1, "counter": 1, "activated": 1}) + admission, err := json.Marshal(map[string]any{"clusterId": "cluster-abc", "epoch": 1, "counter": 1, "activated": 1}) + require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(dir, "admission.json"), admission, 0o600)) assert.Equal(t, http.StatusForbidden, postPlain(), "clustered unauthenticated POST") diff --git a/services/nvpair-workload-manager/push_test.go b/services/nvpair-workload-manager/push_test.go index c50fd7bc..9ec2bcc1 100644 --- a/services/nvpair-workload-manager/push_test.go +++ b/services/nvpair-workload-manager/push_test.go @@ -14,9 +14,11 @@ import ( "github.com/stretchr/testify/require" ) -func lifecycleFrame(id, state, method, origin string) json.RawMessage { +func lifecycleFrame(t *testing.T, id, state, method, origin string) json.RawMessage { + t.Helper() wl := &Workload{ID: id, Model: "m", Engine: "ollama", State: WorkloadState(state), OriginatedFrom: origin, CreatedAt: 1} - p, _ := json.Marshal(lifecycleParams{WorkloadInfo: wl}) + p, err := json.Marshal(lifecycleParams{WorkloadInfo: wl}) + require.NoError(t, err) return json.RawMessage(p) } @@ -35,23 +37,28 @@ func TestResync_BypassesReceiverDedup(t *testing.T) { emitCount := func() int { mu.Lock(); defer mu.Unlock(); return emits } wl := &Workload{ID: "1", Model: "m", Engine: "ollama", RunID: "r1", State: StateRunning, OriginatedFrom: "a", CreatedAt: 1} - wiRaw, _ := json.Marshal(wl) + wiRaw, err := json.Marshal(wl) + require.NoError(t, err) // Normal frame: emitted once. - normal, _ := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: mustJSON(map[string]json.RawMessage{"workloadInfo": wiRaw})}) + normal, err := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: mustJSON(t, map[string]json.RawMessage{"workloadInfo": wiRaw})}) + require.NoError(t, err) assert.Equal(t, http.StatusOK, post(normal)) assert.Equal(t, 1, emitCount(), "emits after first post") // Same frame again: deduped, not re-emitted. assert.Equal(t, http.StatusOK, post(normal)) assert.Equal(t, 1, emitCount(), "repeat post must dedup") // Re-sync frame (same key + state): bypasses dedup, reaches the broker. - resync, _ := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: mustJSON(map[string]json.RawMessage{"workloadInfo": wiRaw, "resync": json.RawMessage("true")})}) + resync, err := json.Marshal(&Message{JSONRPC: "2.0", Method: MethodStarted, Params: mustJSON(t, map[string]json.RawMessage{"workloadInfo": wiRaw, "resync": json.RawMessage("true")})}) + require.NoError(t, err) assert.Equal(t, http.StatusOK, post(resync)) assert.Equal(t, 2, emitCount(), "resync must bypass dedup") } -func mustJSON(v any) json.RawMessage { - b, _ := json.Marshal(v) +func mustJSON(t *testing.T, v any) json.RawMessage { + t.Helper() + b, err := json.Marshal(v) + require.NoError(t, err) return b } @@ -63,12 +70,12 @@ func TestActiveSnapshot_TracksRunningAndTerminalWorkloads(t *testing.T) { k1 := workloadKey{origin: "node-a", id: "1"} k2 := workloadKey{origin: "node-a", id: "2"} - m.trackActive(k1, MethodStarted, lifecycleFrame("1", "running", MethodStarted, "node-a"), StateRunning) - m.trackActive(k2, MethodStarted, lifecycleFrame("2", "running", MethodStarted, "node-a"), StateRunning) + m.trackActive(k1, MethodStarted, lifecycleFrame(t, "1", "running", MethodStarted, "node-a"), StateRunning) + m.trackActive(k2, MethodStarted, lifecycleFrame(t, "2", "running", MethodStarted, "node-a"), StateRunning) assert.Len(t, m.activeSnapshot(), 2) // A terminal is retained (for re-sync redundancy), not dropped. - m.trackActive(k1, MethodErrored, lifecycleFrame("1", "failed", MethodErrored, "node-a"), StateFailed) + m.trackActive(k1, MethodErrored, lifecycleFrame(t, "1", "failed", MethodErrored, "node-a"), StateFailed) assert.Len(t, m.activeSnapshot(), 2, "terminal must be retained") // A removal drops immediately (matches on origin+id). @@ -91,9 +98,9 @@ func TestActiveSnapshot_TracksRunningAndTerminalWorkloads(t *testing.T) { func TestActiveSnapshot_DistinguishesEngineAndRun(t *testing.T) { m := &Manager{activeLocal: make(map[workloadKey]workloadEvent)} - m.trackActive(workloadKey{origin: "a", engine: "ollama", runID: "r1", id: "1"}, MethodStarted, lifecycleFrame("1", "running", MethodStarted, "a"), StateRunning) - m.trackActive(workloadKey{origin: "a", engine: "lmstudio", runID: "r2", id: "1"}, MethodStarted, lifecycleFrame("1", "running", MethodStarted, "a"), StateRunning) - m.trackActive(workloadKey{origin: "a", engine: "ollama", runID: "r3", id: "1"}, MethodStarted, lifecycleFrame("1", "running", MethodStarted, "a"), StateRunning) + m.trackActive(workloadKey{origin: "a", engine: "ollama", runID: "r1", id: "1"}, MethodStarted, lifecycleFrame(t, "1", "running", MethodStarted, "a"), StateRunning) + m.trackActive(workloadKey{origin: "a", engine: "lmstudio", runID: "r2", id: "1"}, MethodStarted, lifecycleFrame(t, "1", "running", MethodStarted, "a"), StateRunning) + m.trackActive(workloadKey{origin: "a", engine: "ollama", runID: "r3", id: "1"}, MethodStarted, lifecycleFrame(t, "1", "running", MethodStarted, "a"), StateRunning) assert.Len(t, m.activeSnapshot(), 3, "engine and runId must keep workloads distinct") m.untrackActive("a", "1") @@ -108,8 +115,8 @@ func TestTrackActive_DoesNotRestoreRunningStateAfterTerminal(t *testing.T) { m := &Manager{activeLocal: make(map[workloadKey]workloadEvent)} k := workloadKey{origin: "a", engine: "ollama", runID: "r1", id: "1"} - m.trackActive(k, MethodCompleted, lifecycleFrame("1", "completed", MethodCompleted, "a"), StateCompleted) - m.trackActive(k, MethodStarted, lifecycleFrame("1", "running", MethodStarted, "a"), StateRunning) + m.trackActive(k, MethodCompleted, lifecycleFrame(t, "1", "completed", MethodCompleted, "a"), StateCompleted) + m.trackActive(k, MethodStarted, lifecycleFrame(t, "1", "running", MethodStarted, "a"), StateRunning) snap := m.activeSnapshot() require.Len(t, snap, 1) diff --git a/services/nvpair-workload-manager/relaysource_test.go b/services/nvpair-workload-manager/relaysource_test.go index 484c1f25..04e49cdc 100644 --- a/services/nvpair-workload-manager/relaysource_test.go +++ b/services/nvpair-workload-manager/relaysource_test.go @@ -28,7 +28,8 @@ func TestSameNameDistinctUUIDPeers_SurviveReplace(t *testing.T) { } src.set([]noderec.DirectoryNode{node("uuid-1", "10.0.0.1"), node("uuid-2", "10.0.0.2")}) - nodes, _ := src.Nodes(context.Background()) + nodes, err := src.Nodes(context.Background()) + require.NoError(t, err) require.Len(t, nodes, 2) ids := map[string]bool{} wantAddresses := map[string][]string{ @@ -65,7 +66,8 @@ func TestPeerDirectory_ExcludesLocalUUID(t *testing.T) { {HostUUID: "self-uuid", Name: "host", IP: "10.0.0.1", Services: svc}, // us {HostUUID: "peer-uuid", Name: "host", IP: "10.0.0.2", Services: svc}, // same name, different machine }) - nodes, _ := src.Nodes(context.Background()) + nodes, err := src.Nodes(context.Background()) + require.NoError(t, err) require.Len(t, nodes, 1) assert.Equal(t, "peer-uuid", nodes[0].ID, "self-filter should drop only our own UUID") } diff --git a/services/shared/clustertrust/clustertrust_test.go b/services/shared/clustertrust/clustertrust_test.go index b5cb9054..d64fd650 100644 --- a/services/shared/clustertrust/clustertrust_test.go +++ b/services/shared/clustertrust/clustertrust_test.go @@ -30,8 +30,10 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM, der []byte) { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "genkey") - serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - uri, _ := url.Parse(nodeURISANPrefix + uuid) + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + require.NoError(t, err, "generate certificate serial") + uri, err := url.Parse(nodeURISANPrefix + uuid) + require.NoError(t, err, "parse certificate URI") tmpl := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: uuid}, @@ -44,7 +46,8 @@ func genLeaf(t *testing.T, uuid string) (certPEM, keyPEM, der []byte) { } der, err = x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv) require.NoError(t, err, "create cert") - keyDER, _ := x509.MarshalPKCS8PrivateKey(priv) + keyDER, err := x509.MarshalPKCS8PrivateKey(priv) + require.NoError(t, err, "marshal private key") certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) keyPEM = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) return certPEM, keyPEM, der @@ -54,7 +57,8 @@ func writePin(t *testing.T, clusterDir, uuid, certPEM string) { t.Helper() dir := filepath.Join(clusterDir, "trusted") require.NoError(t, os.MkdirAll(dir, 0o700), "mkdir trusted") - body, _ := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": certPEM}) + body, err := json.Marshal(map[string]string{"nodeUuid": uuid, "certPem": certPEM}) + require.NoError(t, err, "marshal peer pin") require.NoError(t, os.WriteFile(filepath.Join(dir, uuid+".json"), body, 0o600), "write pin") } @@ -77,8 +81,8 @@ func TestLoadIdentity_ExtractsCertificateAndNodeUUID(t *testing.T) { func TestTrustPin_AuthorizesOnlyMatchingCertificate(t *testing.T) { dir := t.TempDir() selfCert, selfKey, _ := genLeaf(t, "uuid-self") - _ = os.WriteFile(filepath.Join(dir, "node.crt"), selfCert, 0o644) - _ = os.WriteFile(filepath.Join(dir, "node.key"), selfKey, 0o600) + require.NoError(t, os.WriteFile(filepath.Join(dir, "node.crt"), selfCert, 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "node.key"), selfKey, 0o600)) id, err := LoadIdentity(dir) require.NoError(t, err, "LoadIdentity") @@ -181,8 +185,9 @@ func TestTrustReload_SkipsTamperedPins(t *testing.T) { // A tampered file (filename UUID != cert principal) is skipped. cPEM, _, _ := genLeaf(t, "uuid-c") - body, _ := json.Marshal(map[string]string{"nodeUuid": "uuid-wrong", "certPem": string(cPEM)}) - _ = os.WriteFile(filepath.Join(dir, "trusted", "uuid-wrong.json"), body, 0o600) + body, err := json.Marshal(map[string]string{"nodeUuid": "uuid-wrong", "certPem": string(cPEM)}) + require.NoError(t, err, "marshal mismatched peer pin") + require.NoError(t, os.WriteFile(filepath.Join(dir, "trusted", "uuid-wrong.json"), body, 0o600)) tr.Reload() assert.Equal(t, 2, tr.Count(), "tampered pin must be skipped, count") } diff --git a/services/shared/clustertrust/mesh_test.go b/services/shared/clustertrust/mesh_test.go index 09f44606..643f367b 100644 --- a/services/shared/clustertrust/mesh_test.go +++ b/services/shared/clustertrust/mesh_test.go @@ -81,7 +81,8 @@ func TestMeshGate_RequiresMembershipAndPinnedIdentity(t *testing.T) { http.Error(w, "forbidden", http.StatusForbidden) return } - _, _ = w.Write([]byte("ok")) + _, err := w.Write([]byte("ok")) + assert.NoError(t, err, "write peer response") })) srv.TLS = selfMesh.ServerTLSConfig() srv.StartTLS() @@ -116,7 +117,8 @@ func TestMeshGate_RequiresMembershipAndPinnedIdentity(t *testing.T) { require.NoError(t, err, "self->self") assert.Equal(t, http.StatusOK, code, "self->self") // Stranger completes the handshake (it pins self) but self doesn't pin it: 403. - code, _ = do(mustConfig(strangerMesh.ClientTLSConfig("uuid-self"))) + code, err = do(mustConfig(strangerMesh.ClientTLSConfig("uuid-self"))) + require.NoError(t, err, "request as unpinned client") assert.Equal(t, http.StatusForbidden, code, "stranger->self") // self cannot even build a client to an unpinned stranger (client-side gate). _, ok := selfMesh.ClientTLSConfig("uuid-stranger") @@ -141,7 +143,8 @@ func TestMeshServerTLSConfig_TracksMembershipOnExistingListener(t *testing.T) { http.Error(w, "forbidden", http.StatusForbidden) return } - _, _ = w.Write([]byte("ok")) + _, err := w.Write([]byte("ok")) + assert.NoError(t, err, "write peer response") })) srv.TLS = selfMesh.ServerTLSConfig() srv.StartTLS() diff --git a/services/shared/clustertrust/peerclient_test.go b/services/shared/clustertrust/peerclient_test.go index 190a8f95..fc2be6f0 100644 --- a/services/shared/clustertrust/peerclient_test.go +++ b/services/shared/clustertrust/peerclient_test.go @@ -71,7 +71,8 @@ func TestPeerClientPool_ReusesOneConnection(t *testing.T) { http.Error(w, "forbidden", http.StatusForbidden) return } - _, _ = w.Write([]byte("ok")) + _, err := w.Write([]byte("ok")) + assert.NoError(t, err, "write peer response") })) srv.Config.ConnState = func(_ net.Conn, state http.ConnState) { if state == http.StateNew { @@ -94,8 +95,9 @@ func TestPeerClientPool_ReusesOneConnection(t *testing.T) { require.NoError(t, err, "request %d", i) // Drain before closing, exactly as a caller must: an undrained body // leaves the connection unusable and it never returns to the idle pool. - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() + _, err = io.Copy(io.Discard, resp.Body) + require.NoError(t, err, "drain peer response") + require.NoError(t, resp.Body.Close(), "close peer response") assert.Equal(t, http.StatusOK, resp.StatusCode, "request %d", i) } @@ -328,7 +330,8 @@ func TestPeerClientPoolOpts_AllowsStreamingWhenTimeoutIsZero(t *testing.T) { f.Flush() } time.Sleep(150 * time.Millisecond) - _, _ = w.Write([]byte("ok")) + _, err := w.Write([]byte("ok")) + assert.NoError(t, err, "write streaming response") })) srv.TLS = peerMesh.ServerTLSConfig() srv.StartTLS() @@ -342,7 +345,8 @@ func TestPeerClientPoolOpts_AllowsStreamingWhenTimeoutIsZero(t *testing.T) { resp, err := client.Get(srv.URL) require.NoError(t, err, "Timeout=0 must wait out a slow body") defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + require.NoError(t, err, "read streaming response") assert.Equal(t, "ok", string(body)) } diff --git a/services/shared/clustertrust/reload_test.go b/services/shared/clustertrust/reload_test.go index db52cc54..e2c4fe2c 100644 --- a/services/shared/clustertrust/reload_test.go +++ b/services/shared/clustertrust/reload_test.go @@ -38,7 +38,7 @@ func TestReload_KeepsPinsWhenDirUnreadable(t *testing.T) { trusted := filepath.Join(clusterDir, "trusted") require.NoError(t, os.Chmod(trusted, 0o000), "chmod trusted") - t.Cleanup(func() { _ = os.Chmod(trusted, 0o700) }) + t.Cleanup(func() { assert.NoError(t, os.Chmod(trusted, 0o700), "restore trusted directory permissions") }) trust.Reload() _, ok = trust.DER("uuid-peer") diff --git a/services/shared/cors/cors_test.go b/services/shared/cors/cors_test.go index 34bcc0c1..68a67445 100644 --- a/services/shared/cors/cors_test.go +++ b/services/shared/cors/cors_test.go @@ -113,8 +113,10 @@ func TestEndToEndHeaders_StripsHopHeadersAndPreservesRequestHeaders(t *testing.T assert.Equal(t, h.Get("Cookie"), got.Get("Cookie"), "lost end-to-end headers") assert.NotEqual(t, "", h.Get("X-Hop"), "mutated caller") } -func target(s *httptest.Server) Target { - u, _ := url.Parse(s.URL) +func target(t *testing.T, s *httptest.Server) Target { + t.Helper() + u, err := url.Parse(s.URL) + require.NoError(t, err, "parse test server URL") return Target{URL: u, Transport: s.Client().Transport} } func TestFanout_StripsCredentials(t *testing.T) { @@ -133,7 +135,7 @@ func TestFanout_StripsCredentials(t *testing.T) { })) defer server.Close() rec := httptest.NewRecorder() - ServePreflight(rec, request, []Target{target(server), target(server)}) + ServePreflight(rec, request, []Target{target(t, server), target(t, server)}) assert.Equal(t, http.StatusNoContent, rec.Code) assert.Equal(t, "Bearer private", request.Header.Get("Authorization"), "mutated original request") assert.Equal(t, "session=private", request.Header.Get("Cookie"), "mutated original request") @@ -161,7 +163,7 @@ func TestPreflight_CombinesTargetResponses(t *testing.T) { })) defer server.Close() rec := httptest.NewRecorder() - ServePreflight(rec, preflight(), []Target{target(server)}) + ServePreflight(rec, preflight(), []Target{target(t, server)}) assert.Equal(t, status, rec.Code, "single status") assert.Equal(t, withPolicy, rec.Header().Get("Access-Control-Allow-Origin") != "", "single policy changed") if status != http.StatusNoContent { @@ -169,7 +171,7 @@ func TestPreflight_CombinesTargetResponses(t *testing.T) { } assert.Equal(t, int32(1), calls.Load(), "followed redirect") rec = httptest.NewRecorder() - ServePreflight(rec, preflight(), []Target{target(server), target(server)}) + ServePreflight(rec, preflight(), []Target{target(t, server), target(t, server)}) assert.Equal(t, combinedStatus, rec.Code, "combined status") if combinedStatus != http.StatusNoContent { assert.Empty(t, rec.Header().Get("Access-Control-Allow-Origin"), "permission on failure") @@ -193,6 +195,7 @@ func TestPreflight_RejectsRequestWithoutTargets(t *testing.T) { type roundTripFunc func(*http.Request) (*http.Response, error) func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + func TestCORSFanout_BoundsConcurrentRequestsAndHonorsCancellation(t *testing.T) { var active, max atomic.Int32 started := make(chan struct{}, 20) @@ -208,7 +211,8 @@ func TestCORSFanout_BoundsConcurrentRequestsAndHonorsCancellation(t *testing.T) <-r.Context().Done() return nil, r.Context().Err() }) - u, _ := url.Parse("http://engine.test") + u, err := url.Parse("http://engine.test") + require.NoError(t, err, "parse engine URL") targets := make([]Target, 20) for i := range targets { targets[i] = Target{URL: u, Transport: transport} diff --git a/services/shared/httpcon/testclient/client_test.go b/services/shared/httpcon/testclient/client_test.go index cc7166e0..bb90a754 100644 --- a/services/shared/httpcon/testclient/client_test.go +++ b/services/shared/httpcon/testclient/client_test.go @@ -16,7 +16,8 @@ import ( func TestConnectionCounter_CountsDistinctConnections(t *testing.T) { client, connections := New(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - _, _ = io.WriteString(w, "ok") + _, err := io.WriteString(w, "ok") + assert.NoError(t, err, "write response") })) const requests = 2 diff --git a/services/shared/jsonrpc/jsonrpc_test.go b/services/shared/jsonrpc/jsonrpc_test.go index a77b1d52..6ff7d164 100644 --- a/services/shared/jsonrpc/jsonrpc_test.go +++ b/services/shared/jsonrpc/jsonrpc_test.go @@ -80,8 +80,7 @@ func (w *oneByteWriter) Write(p []byte) (int, error) { if len(p) == 0 { return 0, nil } - _ = w.buf.WriteByte(p[0]) - return 1, nil + return w.buf.Write(p[:1]) } func TestShortWrite_WritesFullFrame(t *testing.T) { @@ -128,8 +127,11 @@ func TestRespondError_RoundTripsErrorData(t *testing.T) { assert.Equal(t, "port", data["field"], "error data round-trip failed") // nil data omits the field entirely. var buf2 bytes.Buffer - _ = writeCodec(&buf2).RespondErrorData(&id, -32603, "boom", nil) - m2, _ := readCodec(&buf2).Read() + require.NoError(t, writeCodec(&buf2).RespondErrorData(&id, -32603, "boom", nil), "respond without data") + m2, err := readCodec(&buf2).Read() + require.NoError(t, err, "read response without data") + require.NotNil(t, m2, "response without data") + require.NotNil(t, m2.Error, "error response without data") require.Empty(t, m2.Error.Data, "expected empty data for nil") } diff --git a/services/shared/jsonrpc/peer_test.go b/services/shared/jsonrpc/peer_test.go index 6a0f4f1f..b638b346 100644 --- a/services/shared/jsonrpc/peer_test.go +++ b/services/shared/jsonrpc/peer_test.go @@ -29,8 +29,9 @@ func pipePeers(t *testing.T) (a, b *Peer) { func TestPeerCall_ReturnsResult(t *testing.T) { a, b := pipePeers(t) + writeErr := make(chan error, 1) go b.Serve(func(req *Message) { - go b.Respond(req.ID, map[string]string{"echo": req.Method}) + go func() { writeErr <- b.Respond(req.ID, map[string]string{"echo": req.Method}) }() }, nil) go a.Serve(nil, nil) @@ -38,16 +39,20 @@ func TestPeerCall_ReturnsResult(t *testing.T) { defer cancel() res, rpcErr, err := a.Call(ctx, "ping", json.RawMessage(`{"x":1}`)) require.NoError(t, err, "Call err") + require.NoError(t, <-writeErr, "respond to call") require.Nil(t, rpcErr, "Call err") var out map[string]string - json.Unmarshal(res, &out) + require.NoError(t, json.Unmarshal(res, &out), "decode call result") assert.Equal(t, "ping", out["echo"], "unexpected result") } func TestPeerCall_ReturnsRPCError(t *testing.T) { a, b := pipePeers(t) + writeErr := make(chan error, 1) go b.Serve(func(req *Message) { - go b.RespondError(req.ID, -32601, "method not found") + go func() { + writeErr <- b.RespondError(req.ID, -32601, "method not found") + }() }, nil) go a.Serve(nil, nil) @@ -55,6 +60,7 @@ func TestPeerCall_ReturnsRPCError(t *testing.T) { defer cancel() _, rpcErr, err := a.Call(ctx, "nope", nil) require.NoError(t, err, "transport err") + require.NoError(t, <-writeErr, "respond with RPC error") require.NotNil(t, rpcErr, "want rpc error -32601") assert.Equal(t, -32601, rpcErr.Code) } @@ -63,8 +69,13 @@ func TestPeer_HandlesInboundRequestsWhileCallIsPending(t *testing.T) { // Both peers Call each other while both are serving inbound requests. The // read pump is separate from Call, so neither side deadlocks. a, b := pipePeers(t) + writeErrs := make(chan error, 2) handler := func(self *Peer) func(*Message) { - return func(req *Message) { go self.Respond(req.ID, map[string]string{"from": req.Method}) } + return func(req *Message) { + go func() { + writeErrs <- self.Respond(req.ID, map[string]string{"from": req.Method}) + }() + } } go a.Serve(handler(a), nil) go b.Serve(handler(b), nil) @@ -85,6 +96,7 @@ func TestPeer_HandlesInboundRequestsWhileCallIsPending(t *testing.T) { select { case r := <-ch: require.NoError(t, r.err, "simultaneous call failed") + require.NoError(t, <-writeErrs, "respond to simultaneous call") case <-time.After(3 * time.Second): require.FailNow(t, "simultaneous calls deadlocked") } @@ -152,8 +164,9 @@ func TestPeerCallAfterClose_FailsFast(t *testing.T) { func TestPeer_RelaysRequestAndResponse(t *testing.T) { a, b := pipePeers(t) + writeErr := make(chan error, 1) go b.Serve(func(req *Message) { - go b.Respond(req.ID, map[string]int{"ok": 1}) + go func() { writeErr <- b.Respond(req.ID, map[string]int{"ok": 1}) }() }, nil) go a.Serve(nil, nil) @@ -168,8 +181,9 @@ func TestPeer_RelaysRequestAndResponse(t *testing.T) { select { case r := <-done: require.NoError(t, r.err, "relay err") + require.NoError(t, <-writeErr, "respond to relay") var out map[string]int - json.Unmarshal(r.res, &out) + require.NoError(t, json.Unmarshal(r.res, &out), "decode relay result") assert.Equal(t, 1, out["ok"], "unexpected relay result") case <-time.After(2 * time.Second): require.FailNow(t, "relay respond not invoked") diff --git a/services/shared/noderec/noderec_test.go b/services/shared/noderec/noderec_test.go index db3d59e3..fc31703e 100644 --- a/services/shared/noderec/noderec_test.go +++ b/services/shared/noderec/noderec_test.go @@ -179,7 +179,7 @@ func TestDirectoryNode_RoundTripsJSON(t *testing.T) { } func TestValidateTXTSize_RejectsOversizedEntries(t *testing.T) { - assert.NoError(t, ValidateTXTSize([]string{"v=1", "ni=14318"}), "small TXT flagged") + require.NoError(t, ValidateTXTSize([]string{"v=1", "ni=14318"}), "small TXT flagged") big := "x=" + strings.Repeat("m", 300) assert.Error(t, ValidateTXTSize([]string{"v=1", big}), "oversized TXT entry not flagged") } diff --git a/services/shared/splitlisten/splitlisten_test.go b/services/shared/splitlisten/splitlisten_test.go index cc7cdf8b..06a1154f 100644 --- a/services/shared/splitlisten/splitlisten_test.go +++ b/services/shared/splitlisten/splitlisten_test.go @@ -34,10 +34,12 @@ func newSplitter(t *testing.T) (addr string, cleanup func()) { s := New(ln) plainSrv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - io.WriteString(w, "plain") + _, err := io.WriteString(w, "plain") + assert.NoError(t, err, "write plain response") })} tlsSrv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - io.WriteString(w, "tls") + _, err := io.WriteString(w, "tls") + assert.NoError(t, err, "write TLS response") })} tlsConfig := testServerTLSConfig(t) @@ -83,12 +85,12 @@ func TestMalformedConnection_DoesNotWedgeDispatch(t *testing.T) { // Immediate-close client: connect and close before sending a byte. closer, err := net.Dial("tcp", addr) require.NoError(t, err, "dial closer") - _ = closer.Close() + require.NoError(t, closer.Close(), "close immediate-close client") // A good request must still be served while the silent conn is pending and // after the closed one was dropped. assert.Equal(t, "plain", httpGet(t, "http://"+addr+"/", nil), "plain request after malformed conns served") - _ = silent.Close() + require.NoError(t, silent.Close(), "close silent client") } func TestClose_StopsAccepting(t *testing.T) { @@ -174,7 +176,8 @@ func httpGet(t *testing.T, url string, tlsCfg *tls.Config) string { continue } defer resp.Body.Close() - b, _ := io.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) + require.NoError(t, err, "read HTTP response") return string(b) } require.NoError(t, lastErr, "GET %s", url) @@ -328,12 +331,15 @@ func TestTemporaryAccept_Retries(t *testing.T) { }() // First byte 'G' (GET) routes to plain. + writeErr := make(chan error, 1) go func() { - _, _ = client.Write([]byte("G")) + _, err := client.Write([]byte("G")) + writeErr <- err }() select { case c := <-accepted: + require.NoError(t, <-writeErr, "write initial request byte") _ = c.Close() case <-time.After(2 * time.Second): require.FailNow(t, "plain Accept did not receive connection after temporary Accept error") @@ -363,9 +369,10 @@ func TestPushTimeout_ClosesConnWhenAcceptStops(t *testing.T) { if !assert.NoError(t, derr, "dial") { return } - _, _ = c.Write([]byte("G")) + _, err := c.Write([]byte("G")) + assert.NoError(t, err, "write initial request byte") buf := make([]byte, 1) - _ = c.SetReadDeadline(time.Now().Add(2 * time.Second)) + assert.NoError(t, c.SetReadDeadline(time.Now().Add(2*time.Second)), "set read deadline") _, rerr := c.Read(buf) _ = c.Close() assert.Error(t, rerr, "expected peer close after push timeout, got successful read") @@ -382,8 +389,9 @@ func TestPushTimeout_ClosesConnWhenAcceptStops(t *testing.T) { // handshake (not refuse), then be closed by the same timeout path. c2, err := net.DialTimeout("tcp", addr, time.Second) require.NoError(t, err, "dial after push timeout") - _, _ = c2.Write([]byte("G")) - _ = c2.SetReadDeadline(time.Now().Add(2 * time.Second)) + _, err = c2.Write([]byte("G")) + require.NoError(t, err, "write request after push timeout") + require.NoError(t, c2.SetReadDeadline(time.Now().Add(2*time.Second)), "set read deadline") buf := make([]byte, 1) _, rerr := c2.Read(buf) _ = c2.Close() diff --git a/services/tests/broker_management_test.go b/services/tests/broker_management_test.go index 9c91e725..43e49fe6 100644 --- a/services/tests/broker_management_test.go +++ b/services/tests/broker_management_test.go @@ -24,6 +24,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/appdir" @@ -45,7 +46,8 @@ func fakeOllama(t *testing.T) func() { mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) mux.HandleFunc("/api/tags", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = io.WriteString(w, `{"models":[{"name":"llama3.2:latest"}]}`) + _, writeErr := io.WriteString(w, `{"models":[{"name":"llama3.2:latest"}]}`) + assert.NoError(t, writeErr) }) srv := &http.Server{Handler: mux} go func() { _ = srv.Serve(ln) }() @@ -65,7 +67,8 @@ func fakeLMStudio(t *testing.T) func() { mux := http.NewServeMux() mux.HandleFunc("/v1/models", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = io.WriteString(w, `{"object":"list","data":[{"id":"qwen2.5-7b-instruct","object":"model"}]}`) + _, writeErr := io.WriteString(w, `{"object":"list","data":[{"id":"qwen2.5-7b-instruct","object":"model"}]}`) + assert.NoError(t, writeErr) }) srv := &http.Server{Handler: mux} go func() { _ = srv.Serve(ln) }() @@ -99,7 +102,8 @@ func fakeNodeInfo(t *testing.T, hostUUID string) func() { mux := http.NewServeMux() mux.HandleFunc("/v1/node-info", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - _, _ = io.WriteString(w, fmt.Sprintf(`{"GPUs":[],"hostUuid":%q}`, hostUUID)) + _, writeErr := io.WriteString(w, fmt.Sprintf(`{"GPUs":[],"hostUuid":%q}`, hostUUID)) + assert.NoError(t, writeErr) }) srv := &http.Server{Handler: mux} go func() { _ = srv.Serve(ln) }() @@ -122,9 +126,7 @@ type proxyNodesResult struct { func proxyNodesHas(t *testing.T, raw json.RawMessage, id string) bool { t.Helper() var res proxyNodesResult - if err := json.Unmarshal(raw, &res); err != nil { - return false - } + require.NoError(t, json.Unmarshal(raw, &res)) for _, n := range res.Nodes { if n.ID == id { return true diff --git a/services/tests/broker_supervision_test.go b/services/tests/broker_supervision_test.go index 0937fd11..5aad1cc8 100644 --- a/services/tests/broker_supervision_test.go +++ b/services/tests/broker_supervision_test.go @@ -93,7 +93,7 @@ func startBrokerWithDirsAndEnv(t *testing.T, configDir, clusterDir string, extra require.NoError(t, cmd.Start(), "start broker") t.Logf("broker started: pid=%d", cmd.Process.Pid) - ch := startMsgReader(stdoutPipe) + ch := startMsgReader(t, stdoutPipe) lines := startLineReader(stderrPipe) return stdinPipe, ch, lines, func() { @@ -346,7 +346,7 @@ func TestBroker_ShutsDownOnSignal(t *testing.T) { stdoutPipe, err := cmd.StdoutPipe() require.NoError(t, err, "broker stdout pipe") require.NoError(t, cmd.Start(), "start broker") - msgs := startMsgReader(stdoutPipe) + msgs := startMsgReader(t, stdoutPipe) waitForMethod(t, msgs, "app:ready", 10*time.Second) // SIGINT to the broker only (children are in their own process groups). @@ -481,7 +481,7 @@ func TestBrokerProxySetPort_Rebinds(t *testing.T) { <-done } }) - msgs := startMsgReader(stdoutPipe) + msgs := startMsgReader(t, stdoutPipe) waitForMethod(t, msgs, "app:ready", 10*time.Second) @@ -553,7 +553,8 @@ func TestBroker_MergesManualNodesIntoDiscovery(t *testing.T) { require.True(t, ok, "broker stream closed before the manual node appeared") if msg.Method == "" && msg.ID != nil { var res availableNodesResult - if json.Unmarshal(msg.Result, &res) == nil && containsNode(res.Nodes, nodeName) { + require.NoError(t, json.Unmarshal(msg.Result, &res)) + if containsNode(res.Nodes, nodeName) { t.Logf("manual node %q present in discovery snapshot", nodeName) return } diff --git a/services/tests/broker_test.go b/services/tests/broker_test.go index 297481f5..291a7866 100644 --- a/services/tests/broker_test.go +++ b/services/tests/broker_test.go @@ -72,7 +72,7 @@ func startBroker(t *testing.T) (stdin io.WriteCloser, msgs <-chan jsonrpc.Messag require.NoError(t, cmd.Start(), "start broker") t.Logf("broker started: pid=%d", cmd.Process.Pid) - ch := startMsgReader(stdoutPipe) + ch := startMsgReader(t, stdoutPipe) return stdinPipe, ch, func() { stdinPipe.Close() @@ -140,7 +140,8 @@ func pollNodeListed(t *testing.T, stdin io.Writer, msgs <-chan jsonrpc.Message, require.False(t, allowPush != nil && !allowPush(nodes), "unexpected discovery:nodes-changed") case msg.Method == "" && msg.ID != nil: var res availableNodesResult - if json.Unmarshal(msg.Result, &res) == nil && containsNode(res.Nodes, instance) { + require.NoError(t, json.Unmarshal(msg.Result, &res)) + if containsNode(res.Nodes, instance) { return } } @@ -212,10 +213,11 @@ func TestBroker_RelaysDiscoverySubscriptionAcrossProcesses(t *testing.T) { switch { case msg.Method == "" && msg.ID != nil: var sr subscriptionResult - if json.Unmarshal(msg.Result, &sr) == nil { - assert.True(t, sr.Subscribed, "subscribe ack subscribed=false, want true") - gotAck = true - } + require.NoError(t, json.Unmarshal(msg.Result, &sr)) + + assert.True(t, sr.Subscribed, "subscribe ack subscribed=false, want true") + gotAck = true + case msg.Method == "discovery:nodes-changed": var nodes []availableNode require.NoError(t, json.Unmarshal(msg.Params, &nodes), "unmarshal baseline") @@ -246,10 +248,11 @@ func TestBroker_RelaysDiscoverySubscriptionAcrossProcesses(t *testing.T) { // effect; ignore them and wait for the ack. if msg.Method == "" && msg.ID != nil { var sr subscriptionResult - if json.Unmarshal(msg.Result, &sr) == nil { - assert.False(t, sr.Subscribed, "unsubscribe ack subscribed=true, want false") - ackd = true - } + require.NoError(t, json.Unmarshal(msg.Result, &sr)) + + assert.False(t, sr.Subscribed, "unsubscribe ack subscribed=true, want false") + ackd = true + } case <-deadline: require.FailNow(t, "timed out waiting for unsubscribe ack") diff --git a/services/tests/cluster_data_plane_test.go b/services/tests/cluster_data_plane_test.go index def1aa98..2e438028 100644 --- a/services/tests/cluster_data_plane_test.go +++ b/services/tests/cluster_data_plane_test.go @@ -27,6 +27,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/clustertrust" @@ -98,9 +99,10 @@ func (c *interNodeCluster) startStubClusterPeer(t *testing.T, instance, hostUUID http.Error(w, "forbidden: not a pinned cluster peer", http.StatusForbidden) return } - body, _ := io.ReadAll(r.Body) + body, readErr := io.ReadAll(r.Body) + assert.NoError(t, readErr) var msg jsonrpc.Message - if json.Unmarshal(body, &msg) == nil { + if assert.NoError(t, json.Unmarshal(body, &msg)) { select { case received <- msg: default: @@ -157,7 +159,8 @@ func assertPlaintextRefused(t *testing.T, hostPath, body string) { return } defer resp.Body.Close() - io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) require.GreaterOrEqual(t, resp.StatusCode, 400, "plaintext request to (%v)", hostPath) t.Logf("plaintext request rejected with HTTP %d", resp.StatusCode) } @@ -230,7 +233,8 @@ func TestModelInventory_RefusesLANPlaintext(t *testing.T) { // Loopback: this node's own scanner path must keep working while unclustered. resp, err := client.Get("http://127.0.0.1:14322/v1/models") require.NoError(t, err, "loopback model fetch failed") - body, _ := io.ReadAll(resp.Body) + body, readErr := io.ReadAll(resp.Body) + require.NoError(t, readErr) resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode, "loopback model fetch: HTTP") t.Logf("loopback model fetch OK: %s", strings.TrimSpace(string(body))) @@ -242,7 +246,8 @@ func TestModelInventory_RefusesLANPlaintext(t *testing.T) { t.Logf("LAN plaintext model fetch refused at the transport: %v", err) return } - lanBody, _ := io.ReadAll(lanResp.Body) + lanBody, readErr := io.ReadAll(lanResp.Body) + require.NoError(t, readErr) lanResp.Body.Close() require.Equal(t, http.StatusForbidden, lanResp.StatusCode, "LAN plaintext model fetch: %s", lanBody) } @@ -306,7 +311,8 @@ func assertForbidden(t *testing.T, client *http.Client, url, body string) { return } defer resp.Body.Close() - io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) require.Equal(t, http.StatusForbidden, resp.StatusCode, "unpinned cluster peer got HTTP (%v)", url) } @@ -320,7 +326,8 @@ func postUntil(t *testing.T, client *http.Client, url, body string, want int, ti resp, err := client.Post(url, "application/json", strings.NewReader(body)) if err == nil { code := resp.StatusCode - io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) resp.Body.Close() if code == want { return diff --git a/services/tests/cluster_manager_test.go b/services/tests/cluster_manager_test.go index 85c1ccac..7a6a53a0 100644 --- a/services/tests/cluster_manager_test.go +++ b/services/tests/cluster_manager_test.go @@ -48,7 +48,7 @@ func startCMEnv(t *testing.T, configDir string, port int, extraEnv []string) *cm require.NoError(t, err, "stdout pipe") cmd.Stderr = os.Stderr require.NoError(t, cmd.Start(), "start cluster-manager") - return &cmProc{t: t, cmd: cmd, stdin: stdin, msgs: startMsgReader(stdout), nextID: 1} + return &cmProc{t: t, cmd: cmd, stdin: stdin, msgs: startMsgReader(t, stdout), nextID: 1} } func (p *cmProc) stop() { @@ -86,12 +86,14 @@ func (p *cmProc) pump(want func(jsonrpc.Message) bool, timeout time.Duration) js } } -func idEquals(raw *json.RawMessage, id int) bool { +func idEquals(t *testing.T, raw *json.RawMessage, id int) bool { + t.Helper() if raw == nil { return false } var got int - return json.Unmarshal(*raw, &got) == nil && got == id + require.NoError(t, json.Unmarshal(*raw, &got)) + return got == id } func (p *cmProc) call(method string, params any) jsonrpc.Message { @@ -102,11 +104,12 @@ func (p *cmProc) call(method string, params any) jsonrpc.Message { if params != nil { req["params"] = params } - b, _ := json.Marshal(req) + b, err := json.Marshal(req) + require.NoError(p.t, err, "encode %s", method) b = append(b, '\n') - _, err := p.stdin.Write(b) + _, err = p.stdin.Write(b) require.NoError(p.t, err, "write %s", method) - resp := p.pump(func(m jsonrpc.Message) bool { return m.Method == "" && idEquals(m.ID, id) }, 15*time.Second) + resp := p.pump(func(m jsonrpc.Message) bool { return m.Method == "" && idEquals(p.t, m.ID, id) }, 15*time.Second) require.Nil(p.t, resp.Error, "%s returned a JSON-RPC error", method) return resp } @@ -121,11 +124,12 @@ func (p *cmProc) callExpectError(method string, params any) jsonrpc.Message { if params != nil { req["params"] = params } - b, _ := json.Marshal(req) + b, err := json.Marshal(req) + require.NoError(p.t, err, "encode %s", method) b = append(b, '\n') - _, err := p.stdin.Write(b) + _, err = p.stdin.Write(b) require.NoError(p.t, err, "write %s", method) - return p.pump(func(m jsonrpc.Message) bool { return m.Method == "" && idEquals(m.ID, id) }, 15*time.Second) + return p.pump(func(m jsonrpc.Message) bool { return m.Method == "" && idEquals(p.t, m.ID, id) }, 15*time.Second) } func (p *cmProc) waitNotify(method string) jsonrpc.Message { diff --git a/services/tests/cluster_restore_test.go b/services/tests/cluster_restore_test.go index f25a916e..a6e6a873 100644 --- a/services/tests/cluster_restore_test.go +++ b/services/tests/cluster_restore_test.go @@ -41,7 +41,7 @@ func startBrokerInDir(t *testing.T, configDir string, extraArgs ...string) (io.W require.NoError(t, err, "broker stdout pipe") cmd.Stderr = os.Stderr require.NoError(t, cmd.Start(), "start broker") - msgs := startMsgReader(stdout) + msgs := startMsgReader(t, stdout) cleanup := func() { stdin.Close() done := make(chan error, 1) @@ -65,7 +65,7 @@ func waitForResponseID(t *testing.T, msgs <-chan jsonrpc.Message, id int, timeou select { case msg, ok := <-msgs: require.True(t, ok, "broker stream closed before response id (%v)", id) - if msg.Method == "" && idEquals(msg.ID, id) { + if msg.Method == "" && idEquals(t, msg.ID, id) { return msg } case <-timer.C: @@ -148,7 +148,8 @@ func waitSettingClusterID(t *testing.T, stdin io.Writer, msgs <-chan jsonrpc.Mes var r struct { Value string `json:"value"` } - if resp.Error == nil && json.Unmarshal(resp.Result, &r) == nil { + if resp.Error == nil { + require.NoError(t, json.Unmarshal(resp.Result, &r)) last = r.Value if r.Value == want { return diff --git a/services/tests/discovery_test.go b/services/tests/discovery_test.go index caf337e5..3b4f441e 100644 --- a/services/tests/discovery_test.go +++ b/services/tests/discovery_test.go @@ -68,7 +68,7 @@ func TestDiscovery_ResolvesMultipleRegisteredNodes(t *testing.T) { var servers []*zeroconf.Server for i, inst := range instances { s, err := zeroconf.Register(inst, service, testDomain, 50000+i, nil, nil) - require.NoError(t, err, "register (%v, %v)", inst, err) + require.NoError(t, err, "register %s", inst) servers = append(servers, s) defer s.Shutdown() } diff --git a/services/tests/errors_test.go b/services/tests/errors_test.go index 2d8e1069..eaac8319 100644 --- a/services/tests/errors_test.go +++ b/services/tests/errors_test.go @@ -6,6 +6,7 @@ package tests import ( "bufio" "encoding/json" + stdErrors "errors" "io" "os" "os/exec" @@ -15,6 +16,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/errors" @@ -50,7 +52,7 @@ func TestErrorsBrokerPipeline_RelaysReportsUpdatesAndClearsAcrossProcesses(t *te _ = cmd.Wait() }() - out := startBrokerStream(stdout) + out := startBrokerStream(t, stdout) // First frame: the subprocess emits "ready" right after startup. waitForMethodOrTimeout(t, out, "ready", 5*time.Second) @@ -61,7 +63,8 @@ func TestErrorsBrokerPipeline_RelaysReportsUpdatesAndClearsAcrossProcesses(t *te // scoped to local-origin entries for cross-node correctness) // resolves against the reports — a fixed fake nodeId would no // longer match the binary's real hostname. - localNode, _ := os.Hostname() + localNode, err := os.Hostname() + require.NoError(t, err) // 1. errors:report (REQUEST form, id=1). Expect a null response, // then an errors:update notification with the new entry. @@ -146,7 +149,8 @@ func TestErrorsBrokerPipeline_RelaysReportsUpdatesAndClearsAcrossProcesses(t *te // startBrokerStream is a jsonrpc.Message-emitting version of // startMsgReader. Inline to avoid touching the shared helper, which // other tests use with the slimmer jsonrpc.Message shape. -func startBrokerStream(r io.Reader) <-chan jsonrpc.Message { +func startBrokerStream(t *testing.T, r io.Reader) <-chan jsonrpc.Message { + t.Helper() ch := make(chan jsonrpc.Message, 64) go func() { defer close(ch) @@ -154,17 +158,13 @@ func startBrokerStream(r io.Reader) <-chan jsonrpc.Message { scanner.Buffer(make([]byte, 0, 256*1024), 256*1024) for scanner.Scan() { var msg jsonrpc.Message - if json.Unmarshal(scanner.Bytes(), &msg) == nil { + if assert.NoError(t, json.Unmarshal(scanner.Bytes(), &msg)) { ch <- msg } } - // Scanner exits on EOF, ErrTooLong, or an underlying - // read error. Surface the read-error case so a hung - // subprocess doesn't masquerade as a clean close — but - // only as a log line, since failing the test from a - // goroutine on EOF-vs-error confusion would mask the - // real assertion failure. - _ = scanner.Err() + if err := scanner.Err(); !stdErrors.Is(err, os.ErrClosed) { + assert.NoError(t, err) + } }() return ch } diff --git a/services/tests/ghost_node_test.go b/services/tests/ghost_node_test.go index f0d741df..1355d256 100644 --- a/services/tests/ghost_node_test.go +++ b/services/tests/ghost_node_test.go @@ -17,6 +17,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grandcat/zeroconf" @@ -63,7 +64,8 @@ func TestScanner_EvictsRecordSupersededAtItsAddress(t *testing.T) { return } w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"GPUs":[],"hostUuid":"` + reported.Load().(string) + `"}`)) + _, writeErr := w.Write([]byte(`{"GPUs":[],"hostUuid":"` + reported.Load().(string) + `"}`)) + assert.NoError(t, writeErr) })) defer nodeInfo.Close() @@ -92,7 +94,7 @@ func TestScanner_EvictsRecordSupersededAtItsAddress(t *testing.T) { // The scanner must see the peer before there is anything to evict. A slow // or lossy multicast environment is an inconclusive run, not a failure. - if !awaitNodeEvent(events, noderec.NotifyNodeDiscovered, originalUUID, 30*time.Second) { + if !awaitNodeEvent(t, events, noderec.NotifyNodeDiscovered, originalUUID, 30*time.Second) { t.Skip("peer record was never discovered; multicast is unavailable in this environment") } @@ -107,7 +109,7 @@ func TestScanner_EvictsRecordSupersededAtItsAddress(t *testing.T) { // inference load — so this deadline is generous by design. The identity probe // runs on every scan from ~15s in, so the mismatch is detected long before the // eviction it eventually authorizes. - require.True(t, awaitNodeEvent(events, noderec.NotifyNodeRemoved, originalUUID, 2*time.Minute)) + require.True(t, awaitNodeEvent(t, events, noderec.NotifyNodeRemoved, originalUUID, 2*time.Minute)) } // startScannerForGhostTest runs the scanner binary on stdio and returns its @@ -134,13 +136,14 @@ func startScannerForGhostTest(t *testing.T) <-chan jsonrpc.Message { _ = cmd.Process.Kill() _ = cmd.Wait() }) - return startMsgReader(stdout) + return startMsgReader(t, stdout) } // awaitNodeEvent reports whether the named discovery:node-* event for hostUUID // arrives before the deadline. Other events (this node's own record, the peer's // enrichment updates) are ignored. -func awaitNodeEvent(events <-chan jsonrpc.Message, method, hostUUID string, timeout time.Duration) bool { +func awaitNodeEvent(t *testing.T, events <-chan jsonrpc.Message, method, hostUUID string, timeout time.Duration) bool { + t.Helper() deadline := time.After(timeout) for { select { @@ -152,9 +155,7 @@ func awaitNodeEvent(events <-chan jsonrpc.Message, method, hostUUID string, time continue } var ev noderec.NodeEvent - if err := json.Unmarshal(msg.Params, &ev); err != nil { - continue - } + require.NoError(t, json.Unmarshal(msg.Params, &ev)) if ev.Node.HostUUID == hostUUID { return true } diff --git a/services/tests/llamacpp_interop_test.go b/services/tests/llamacpp_interop_test.go index 612688c4..b64e6c06 100644 --- a/services/tests/llamacpp_interop_test.go +++ b/services/tests/llamacpp_interop_test.go @@ -67,14 +67,16 @@ func TestLlamaCPPFacade_UsesRouterInventoryAndExactModelIDs(t *testing.T) { switch { case r.Method == http.MethodGet && r.URL.Path == "/models": modelListHits.Add(1) - _ = json.NewEncoder(w).Encode(map[string]any{ + assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{ "object": "list", "data": []map[string]string{{"id": model}}, - }) + })) case r.Method == http.MethodPost && r.URL.Path == "/v1/chat/completions": inferenceHits.Add(1) - _, _ = io.Copy(io.Discard, r.Body) - _, _ = io.WriteString(w, `{"choices":[]}`) + _, err := io.Copy(io.Discard, r.Body) + assert.NoError(t, err, "read inference request") + _, err = io.WriteString(w, `{"choices":[]}`) + assert.NoError(t, err, "write inference response") default: http.NotFound(w, r) } @@ -102,36 +104,33 @@ func TestLlamaCPPFacade_UsesRouterInventoryAndExactModelIDs(t *testing.T) { client := &http.Client{Timeout: 5 * time.Second} t.Cleanup(client.CloseIdleConnections) - getModelList := func(t *testing.T, path string) { - t.Helper() - hitsBefore := modelListHits.Load() - response, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d%s", proxyPort, path)) - require.NoError(t, err, "get model list") - defer response.Body.Close() - var list struct { - Object string `json:"object"` - Data []struct { - ID string `json:"id"` - } `json:"data"` - } - require.NoError(t, json.NewDecoder(response.Body).Decode(&list), "decode model list") - found := false - for _, item := range list.Data { - if item.ID == model { - found = true + getModelList := func(name, path string) { + t.Run(name, func(t *testing.T) { + hitsBefore := modelListHits.Load() + response, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d%s", proxyPort, path)) + require.NoError(t, err, "get model list") + defer func() { require.NoError(t, response.Body.Close(), "close model-list response") }() + var list struct { + Object string `json:"object"` + Data []struct { + ID string `json:"id"` + } `json:"data"` } - } - assert.Equal(t, http.StatusOK, response.StatusCode) - assert.Equal(t, "list", list.Object) - assert.True(t, found, "model list must include the advertised model") - assert.Equal(t, hitsBefore+1, modelListHits.Load(), "upstream model-list requests") + require.NoError(t, json.NewDecoder(response.Body).Decode(&list), "decode model list") + found := false + for _, item := range list.Data { + if item.ID == model { + found = true + } + } + assert.Equal(t, http.StatusOK, response.StatusCode) + assert.Equal(t, "list", list.Object) + assert.True(t, found, "model list must include the advertised model") + assert.Equal(t, hitsBefore+1, modelListHits.Load(), "upstream model-list requests") + }) } - t.Run("remaps OpenAI model list to router inventory", func(t *testing.T) { - getModelList(t, "/v1/models") - }) - t.Run("serves router model-list alias", func(t *testing.T) { - getModelList(t, "/models") - }) + getModelList("remaps OpenAI model list to router inventory", "/v1/models") + getModelList("serves router model-list alias", "/models") post := func(t *testing.T, requestedModel string) int { t.Helper() diff --git a/services/tests/log_level_test.go b/services/tests/log_level_test.go index 3d5cd869..708fc8e2 100644 --- a/services/tests/log_level_test.go +++ b/services/tests/log_level_test.go @@ -66,7 +66,7 @@ func startProxyWithLog(t *testing.T, level string) (stdin io.WriteCloser, msgs < require.NoError(t, cmd.Start(), "start proxy") t.Logf("proxy started: pid=%d log-level=%s", cmd.Process.Pid, level) - ch := startMsgReader(stdoutPipe) + ch := startMsgReader(t, stdoutPipe) // Enable a facade so the process behaves like one the broker brought up: // it binds, announces ready, and starts its background work, which is what @@ -213,7 +213,7 @@ func TestLogLevel_UsesEnvironmentDefault(t *testing.T) { stdoutPipe, err := cmd.StdoutPipe() require.NoError(t, err, "stdout pipe") require.NoError(t, cmd.Start(), "start") - msgs := startMsgReader(stdoutPipe) + msgs := startMsgReader(t, stdoutPipe) t.Cleanup(func() { stdinPipe.Close() done := make(chan error, 1) diff --git a/services/tests/main_test.go b/services/tests/main_test.go index 9475d2f5..00736536 100644 --- a/services/tests/main_test.go +++ b/services/tests/main_test.go @@ -6,6 +6,7 @@ package tests import ( "bufio" "encoding/json" + "errors" "io" "log" "os" @@ -16,6 +17,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/appdir" @@ -173,7 +175,8 @@ func goBuild(srcDir, output string) error { // nvpair-shared/jsonrpc types (jsonrpc.Message / jsonrpc.RPCError), so the // integration tests parse exactly what production emits. -func startMsgReader(r io.Reader) <-chan jsonrpc.Message { +func startMsgReader(t *testing.T, r io.Reader) <-chan jsonrpc.Message { + t.Helper() ch := make(chan jsonrpc.Message, 64) go func() { defer close(ch) @@ -181,10 +184,13 @@ func startMsgReader(r io.Reader) <-chan jsonrpc.Message { scanner.Buffer(make([]byte, 0, 256*1024), 256*1024) for scanner.Scan() { var msg jsonrpc.Message - if json.Unmarshal(scanner.Bytes(), &msg) == nil { + if assert.NoError(t, json.Unmarshal(scanner.Bytes(), &msg)) { ch <- msg } } + if err := scanner.Err(); !errors.Is(err, os.ErrClosed) { + assert.NoError(t, err) + } }() return ch } @@ -271,7 +277,7 @@ func TestLMStudioFacadeChild_PersistsUnderPrivateBase(t *testing.T) { _ = cmd.Process.Kill() _ = cmd.Wait() }() - msgs := startMsgReader(stdout) + msgs := startMsgReader(t, stdout) requestOnFreePort(t, stdin, msgs, 10*time.Second, func(port int) map[string]any { return map[string]any{ diff --git a/services/tests/model_routing_interop_test.go b/services/tests/model_routing_interop_test.go index 5812bebb..64e2357a 100644 --- a/services/tests/model_routing_interop_test.go +++ b/services/tests/model_routing_interop_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -27,14 +28,17 @@ func newRoutingUpstream(t *testing.T, status int) *routingUpstream { upstream := &routingUpstream{} upstream.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { upstream.hits.Add(1) - _, _ = io.Copy(io.Discard, r.Body) + _, copyErr := io.Copy(io.Discard, r.Body) + assert.NoError(t, copyErr) w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if status == http.StatusOK { - _, _ = io.WriteString(w, `{"done":true,"choices":[]}`) + _, writeErr := io.WriteString(w, `{"done":true,"choices":[]}`) + assert.NoError(t, writeErr) return } - _, _ = io.WriteString(w, `{"error":"model not found"}`) + _, writeErr := io.WriteString(w, `{"error":"model not found"}`) + assert.NoError(t, writeErr) })) t.Cleanup(upstream.server.Close) return upstream @@ -112,7 +116,8 @@ func TestModelRouting_RejectsNodesWithoutRequestedModelAcrossProcesses(t *testin resp, err := client.Post(endpoint, "application/json", bytes.NewBufferString(fmt.Sprintf(`{"model":%q,"messages":[]}`, targetModel))) require.NoError(t, err, "target-model request failed") - body, _ := io.ReadAll(resp.Body) + body, readErr := io.ReadAll(resp.Body) + require.NoError(t, readErr) _ = resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode, "target-model response (%v)", body) after := snapshot() @@ -122,7 +127,8 @@ func TestModelRouting_RejectsNodesWithoutRequestedModelAcrossProcesses(t *testin resp, err = client.Post(endpoint, "application/json", bytes.NewBufferString(`{"model":"no-advertised-owner","messages":[]}`)) require.NoError(t, err, "ownerless request failed") - body, _ = io.ReadAll(resp.Body) + body, readErr = io.ReadAll(resp.Body) + require.NoError(t, readErr) _ = resp.Body.Close() require.Equal(t, http.StatusBadGateway, resp.StatusCode, "ownerless response (%v)", body) require.Contains(t, string(body), "no available node advertises the requested model", "ownerless response") diff --git a/services/tests/models_http_test.go b/services/tests/models_http_test.go index a58e0e3f..27f094c9 100644 --- a/services/tests/models_http_test.go +++ b/services/tests/models_http_test.go @@ -20,6 +20,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grandcat/zeroconf" @@ -34,7 +35,7 @@ func TestDiscovery_EnrichesModelsThroughHTTP(t *testing.T) { } // Flat union + per-engine attribution, exactly the shape engine-manager's // ModelsResult serializes. The daemon must enrich both onto the node. - _ = json.NewEncoder(w).Encode(map[string]any{ + assert.NoError(t, json.NewEncoder(w).Encode(map[string]any{ "models": []string{"llama3:8b", "qwen:0.5b", "org/router-model-GGUF:Q4_K_M"}, "modelsByEngine": map[string][]string{ "ollama": {"llama3:8b"}, @@ -46,7 +47,7 @@ func TestDiscovery_EnrichesModelsThroughHTTP(t *testing.T) { "lmstudio": {}, "llamacpp": {"org/router-model-GGUF:Q4_K_M"}, }, - }) + })) })) t.Cleanup(srv.Close) u, err := url.Parse(srv.URL) @@ -79,13 +80,14 @@ func TestDiscovery_EnrichesModelsThroughHTTP(t *testing.T) { require.True(t, ok, "broker stream closed unexpectedly") if msg.Method == "" && msg.ID != nil { var res availableNodesResult - if json.Unmarshal(msg.Result, &res) == nil { - if n, found := findNode(res.Nodes, instance); found && - modelsMatch(n.Models) && modelsByEngineMatch(n.ModelsByEngine) && - loadedByEngineMatch(n.LoadedByEngine) { - return // enriched flat + per-engine + loaded lists surfaced - } + require.NoError(t, json.Unmarshal(msg.Result, &res)) + + if n, found := findNode(res.Nodes, instance); found && + modelsMatch(n.Models) && modelsByEngineMatch(n.ModelsByEngine) && + loadedByEngineMatch(n.LoadedByEngine) { + return // enriched flat + per-engine + loaded lists surfaced } + } case <-ticker.C: id++ diff --git a/services/tests/models_refresh_test.go b/services/tests/models_refresh_test.go index 672d9790..06d307cc 100644 --- a/services/tests/models_refresh_test.go +++ b/services/tests/models_refresh_test.go @@ -22,6 +22,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grandcat/zeroconf" @@ -43,7 +44,8 @@ func (s *swapModels) set(body map[string]any) { s.mu.Unlock() } -func (s *swapModels) handler() http.HandlerFunc { +func (s *swapModels) handler(t *testing.T) http.HandlerFunc { + t.Helper() return func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/models" { http.NotFound(w, r) @@ -52,7 +54,7 @@ func (s *swapModels) handler() http.HandlerFunc { s.mu.Lock() body := s.body s.mu.Unlock() - _ = json.NewEncoder(w).Encode(body) + assert.NoError(t, json.NewEncoder(w).Encode(body)) } } @@ -60,7 +62,7 @@ func TestModelsPeriodicRefresh_ConvergesWithoutMDNSChange(t *testing.T) { // Start with an empty inventory (engine present but serving no models yet). stub := &swapModels{} stub.set(map[string]any{"models": []string{}}) - srv := httptest.NewServer(stub.handler()) + srv := httptest.NewServer(stub.handler(t)) t.Cleanup(srv.Close) u, err := url.Parse(srv.URL) require.NoError(t, err, "parse stub url") @@ -144,11 +146,12 @@ func pollForNode(t *testing.T, stdin io.Writer, msgs <-chan jsonrpc.Message, ins require.True(t, ok, "broker stream closed unexpectedly") if msg.Method == "" && msg.ID != nil { var res availableNodesResult - if json.Unmarshal(msg.Result, &res) == nil { - if n, found := findNode(res.Nodes, instance); found && pred(n) { - return - } + require.NoError(t, json.Unmarshal(msg.Result, &res)) + + if n, found := findNode(res.Nodes, instance); found && pred(n) { + return } + } case <-ticker.C: id++ diff --git a/services/tests/node_settings_test.go b/services/tests/node_settings_test.go index c88f0e25..e948afa4 100644 --- a/services/tests/node_settings_test.go +++ b/services/tests/node_settings_test.go @@ -48,7 +48,7 @@ func startNodeSettings(t *testing.T, settingsPath string) (io.WriteCloser, <-cha require.NoError(t, cmd.Start(), "start node-settings") t.Logf("node-settings started: pid=%d settings=%s", cmd.Process.Pid, settingsPath) - ch := startMsgReader(stdoutPipe) + ch := startMsgReader(t, stdoutPipe) cleanup := func() { stdinPipe.Close() @@ -99,8 +99,11 @@ func callRPC(t *testing.T, in io.Writer, msgs <-chan jsonrpc.Message, method str continue } var gotID int64 - if msg.ID != nil && json.Unmarshal(*msg.ID, &gotID) == nil && gotID == id { - return msg + if msg.ID != nil { + require.NoError(t, json.Unmarshal(*msg.ID, &gotID)) + if gotID == id { + return msg + } } case <-timer.C: require.FailNowf(t, "timed out waiting for response", "method %s (id=%d) after %s", method, id, timeout) @@ -239,21 +242,21 @@ func TestNodeSettings_PersistsAcrossProcessRestart(t *testing.T) { var sync struct { Value bool `json:"value"` } - _ = json.Unmarshal(resp.Result, &sync) + require.NoError(t, json.Unmarshal(resp.Result, &sync)) assert.True(t, sync.Value, "cluster-auto-sync did not survive restart") resp = callRPC(t, in2, msgs2, "settings/get-cluster-id", nil, 3*time.Second) var id struct { Value string `json:"value"` } - _ = json.Unmarshal(resp.Result, &id) + require.NoError(t, json.Unmarshal(resp.Result, &id)) assert.Equal(t, "cluster-abc-123", id.Value, "cluster-id did not survive restart") resp = callRPC(t, in2, msgs2, "settings/get-cluster-friendly-name", nil, 3*time.Second) var name struct { Value string `json:"value"` } - _ = json.Unmarshal(resp.Result, &name) + require.NoError(t, json.Unmarshal(resp.Result, &name)) assert.Equal(t, "Lab 3 desks", name.Value, "cluster-friendly-name did not survive restart") } diff --git a/services/tests/ollama_host_alias_test.go b/services/tests/ollama_host_alias_test.go index 4d37dbd9..610bc1cf 100644 --- a/services/tests/ollama_host_alias_test.go +++ b/services/tests/ollama_host_alias_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -33,7 +34,8 @@ func TestOllamaHostAlias_ForwardsRequestsThroughBrokerManagedProxy(t *testing.T) http.NotFound(w, r) return } - _, _ = io.WriteString(w, `{"message":{"role":"assistant","content":"routed-through-alias"},"done":true}`) + _, writeErr := io.WriteString(w, `{"message":{"role":"assistant","content":"routed-through-alias"},"done":true}`) + assert.NoError(t, writeErr) })) defer upstream.Close() upstreamPort := upstream.Listener.Addr().(*net.TCPAddr).Port @@ -64,8 +66,9 @@ func TestOllamaHostAlias_ForwardsRequestsThroughBrokerManagedProxy(t *testing.T) "application/json", strings.NewReader(`{"model":"alias-e2e-model","messages":[]}`), ) - require.NoError(t, err, "POST through inherited OLLAMA_HOST alias (%v, %v)", host, err) - body, _ := io.ReadAll(resp.Body) + require.NoError(t, err, "POST through inherited OLLAMA_HOST alias") + body, readErr := io.ReadAll(resp.Body) + require.NoError(t, readErr) _ = resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode, "alias response %s", body) require.Contains(t, string(body), "routed-through-alias", "alias response") @@ -89,7 +92,8 @@ func TestOllamaHostAlias_ForwardsRequestsThroughBrokerManagedProxy(t *testing.T) State string `json:"state"` } `json:"workloadInfo"` } - if json.Unmarshal(msg.Params, &p) == nil && p.WorkloadInfo.Model == "alias-e2e-model" { + require.NoError(t, json.Unmarshal(msg.Params, &p)) + if p.WorkloadInfo.Model == "alias-e2e-model" { wantStates[p.WorkloadInfo.State] = true } case <-deadline: diff --git a/services/tests/remote_engine_test.go b/services/tests/remote_engine_test.go index 8cbe7806..618919c1 100644 --- a/services/tests/remote_engine_test.go +++ b/services/tests/remote_engine_test.go @@ -39,7 +39,8 @@ func mintClusterIdentity(t *testing.T, dir, uuid string) string { t.Helper() pub, priv, err := ed25519.GenerateKey(rand.Reader) require.NoError(t, err, "generate key") - san, _ := url.Parse("urn:nvpair:node:" + uuid) + san, err := url.Parse("urn:nvpair:node:" + uuid) + require.NoError(t, err) tmpl := &x509.Certificate{ SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: uuid}, @@ -68,7 +69,8 @@ func writePin(t *testing.T, dir, peerUUID, peerCertPEM string) { t.Helper() trustedDir := filepath.Join(dir, "trusted") require.NoError(t, os.MkdirAll(trustedDir, 0o700), "mkdir trusted") - pin, _ := json.Marshal(map[string]string{"nodeUuid": peerUUID, "certPem": peerCertPEM}) + pin, err := json.Marshal(map[string]string{"nodeUuid": peerUUID, "certPem": peerCertPEM}) + require.NoError(t, err) require.NoError(t, os.WriteFile(filepath.Join(trustedDir, peerUUID+".json"), pin, 0o600), "write pin") } @@ -205,7 +207,7 @@ func startEngineManagerStdio(t *testing.T, clusterDir string) (io.WriteCloser, < stdout, err := cmd.StdoutPipe() require.NoError(t, err, "engine-manager stdout pipe") require.NoError(t, cmd.Start(), "start engine-manager (client)") - msgs := startMsgReader(stdout) + msgs := startMsgReader(t, stdout) return stdin, msgs, func() { _ = stdin.Close() done := make(chan error, 1) diff --git a/services/tests/scheduler_interop_test.go b/services/tests/scheduler_interop_test.go index 9590569d..eb016989 100644 --- a/services/tests/scheduler_interop_test.go +++ b/services/tests/scheduler_interop_test.go @@ -28,6 +28,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/jsonrpc" @@ -45,7 +46,7 @@ func startSchedulerProc(t *testing.T, args ...string) (io.WriteCloser, <-chan js stdout, err := cmd.StdoutPipe() require.NoError(t, err, "scheduler stdout pipe") require.NoError(t, cmd.Start(), "start scheduler") - ch := startMsgReader(stdout) + ch := startMsgReader(t, stdout) var cleanupOnce sync.Once return stdin, ch, func() { cleanupOnce.Do(func() { @@ -76,9 +77,7 @@ func waitForPriorityPair(t *testing.T, ch <-chan jsonrpc.Message, timeout time.D continue } var p schedulerwire.EnginePriority - if json.Unmarshal(msg.Params, &p) != nil { - continue - } + require.NoError(t, json.Unmarshal(msg.Params, &p)) if p.Engine == "ollama" || p.Engine == "lmstudio" { got[p.Engine] = p } @@ -323,7 +322,8 @@ func runBlockedProxyBurst(t *testing.T, pending, pressure []int, requests int) ( return } w.Header().Set("Content-Type", "application/json") - _, _ = io.WriteString(w, `{"done":true}`) + _, writeErr := io.WriteString(w, `{"done":true}`) + assert.NoError(t, writeErr) })) } @@ -414,7 +414,8 @@ func runBlockedProxyBurst(t *testing.T, pending, pressure []int, requests int) ( results <- err return } - _, _ = io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + assert.NoError(t, copyErr) _ = resp.Body.Close() if resp.StatusCode != http.StatusOK { results <- fmt.Errorf("proxy status %d", resp.StatusCode) @@ -527,9 +528,11 @@ func TestLMStudioProxy_IgnoresPriorityNodesAbsentFromDiscovery(t *testing.T) { hitsMu.Lock() hits["real-lm"]++ hitsMu.Unlock() - io.Copy(io.Discard, r.Body) + _, copyErr := io.Copy(io.Discard, r.Body) + assert.NoError(t, copyErr) w.Header().Set("Content-Type", "application/json") - _, _ = io.WriteString(w, `{"choices":[]}`) + _, writeErr := io.WriteString(w, `{"choices":[]}`) + assert.NoError(t, writeErr) })) t.Cleanup(realEngine.Close) realPort := portOfURL(t, realEngine.URL) @@ -564,7 +567,8 @@ func TestLMStudioProxy_IgnoresPriorityNodesAbsentFromDiscovery(t *testing.T) { bytes.NewReader([]byte(`{"model":"chat-model","messages":[]}`)), ) require.NoError(t, err, "chat request failed") - _, _ = io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) _ = resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode, "proxy status") diff --git a/services/tests/secure_inference_test.go b/services/tests/secure_inference_test.go index 23204ddb..0fd77fbb 100644 --- a/services/tests/secure_inference_test.go +++ b/services/tests/secure_inference_test.go @@ -20,6 +20,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/engines" @@ -75,7 +76,7 @@ func startProxyProc(t *testing.T, clusterDir string, listenPort int) *proxyProc require.NoError(t, err, "proxy stdout pipe") cmd.Stderr = os.Stderr require.NoError(t, cmd.Start(), "start proxy") - p := &proxyProc{t: t, cmd: cmd, stdin: stdin, msgs: startMsgReader(stdout), nextID: 1} + p := &proxyProc{t: t, cmd: cmd, stdin: stdin, msgs: startMsgReader(t, stdout), nextID: 1} // The mTLS ingress under test is engine-agnostic; Ollama is an arbitrary // pick. The bound port comes from the enable response rather than the ready @@ -138,11 +139,12 @@ func (p *proxyProc) call(method string, params any) jsonrpc.Message { if params != nil { req["params"] = params } - b, _ := json.Marshal(req) + b, err := json.Marshal(req) + require.NoError(p.t, err, "encode %s", method) b = append(b, '\n') - _, err := p.stdin.Write(b) + _, err = p.stdin.Write(b) require.NoError(p.t, err, "write %s", method) - resp := p.pump(func(m jsonrpc.Message) bool { return m.Method == "" && idEquals(m.ID, id) }, 15*time.Second) + resp := p.pump(func(m jsonrpc.Message) bool { return m.Method == "" && idEquals(p.t, m.ID, id) }, 15*time.Second) require.Nil(p.t, resp.Error, "%s returned a JSON-RPC error", method) return resp } @@ -153,9 +155,10 @@ func (p *proxyProc) notify(method string, params any) { if params != nil { req["params"] = params } - b, _ := json.Marshal(req) + b, err := json.Marshal(req) + require.NoError(p.t, err, "encode %s", method) b = append(b, '\n') - _, err := p.stdin.Write(b) + _, err = p.stdin.Write(b) require.NoError(p.t, err, "notify %s", method) } @@ -196,7 +199,8 @@ func (p *proxyProc) waitForRoutableNode(t *testing.T) { var r struct { Nodes []json.RawMessage `json:"nodes"` } - if json.Unmarshal(resp.Result, &r) == nil && len(r.Nodes) > 0 { + require.NoError(t, json.Unmarshal(resp.Result, &r)) + if len(r.Nodes) > 0 { return } time.Sleep(100 * time.Millisecond) @@ -214,11 +218,13 @@ func startFakeOllama(t *testing.T) (host string, port int, generates *int32) { switch { case r.Method == http.MethodGet && r.URL.Path == "/api/tags": w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{"models":[{"name":"m:latest","model":"m:latest"}]}`) + _, writeErr := io.WriteString(w, `{"models":[{"name":"m:latest","model":"m:latest"}]}`) + assert.NoError(t, writeErr) case r.Method == http.MethodPost && r.URL.Path == "/api/generate": atomic.AddInt32(&n, 1) w.Header().Set("Content-Type", "application/json") - io.WriteString(w, `{"model":"m:latest","response":"hello from the backend","done":true}`) + _, writeErr := io.WriteString(w, `{"model":"m:latest","response":"hello from the backend","done":true}`) + assert.NoError(t, writeErr) default: w.WriteHeader(http.StatusNotFound) } @@ -226,7 +232,8 @@ func startFakeOllama(t *testing.T) (host string, port int, generates *int32) { t.Cleanup(srv.Close) u, err := url.Parse(srv.URL) require.NoError(t, err, "parse fake engine url") - p, _ := strconv.Atoi(u.Port()) + p, err := strconv.Atoi(u.Port()) + require.NoError(t, err) return u.Hostname(), p, &n } @@ -277,7 +284,8 @@ func TestClusterInference_RequiresPinnedMutualTLS(t *testing.T) { before := atomic.LoadInt32(bGenerates) resp := postInference(t, fmt.Sprintf("http://127.0.0.1:%d/api/generate", proxyA.port), genBody) defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, readErr := io.ReadAll(resp.Body) + require.NoError(t, readErr) require.Equal(t, http.StatusOK, resp.StatusCode, "A->B inference status (%v)", body) require.Contains(t, string(body), "hello from the backend", "A->B response did not come from B's engine") require.Equal(t, before+1, atomic.LoadInt32(bGenerates), "request must reach B's engine") @@ -292,7 +300,8 @@ func TestClusterInference_RequiresPinnedMutualTLS(t *testing.T) { require.NoError(t, err, "foreign C dial B ingress") defer resp.Body.Close() if resp.StatusCode != http.StatusForbidden { - b, _ := io.ReadAll(resp.Body) + b, readErr := io.ReadAll(resp.Body) + require.NoError(t, readErr) require.FailNow(t, fmt.Sprintf("foreign C status = %d, want 403; body=%s", resp.StatusCode, b)) } }) diff --git a/services/tests/tls_test.go b/services/tests/tls_test.go index 5648ff15..c9712337 100644 --- a/services/tests/tls_test.go +++ b/services/tests/tls_test.go @@ -244,7 +244,8 @@ func TestNodeInfoHTTPS_RequiresClientCertificateWhenConfigured(t *testing.T) { url := fmt.Sprintf("https://localhost:%d/v1/node-info", tlsPort) resp, err := client.Get(url) require.NoError(t, err, "authenticated mTLS GET (%v)", url) - body, _ := io.ReadAll(resp.Body) + body, readErr := io.ReadAll(resp.Body) + require.NoError(t, readErr) resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode, "authenticated mTLS GET status (%v)", body) assert.Contains(t, string(body), "GPUs", "response body missing GPUs key") diff --git a/services/tests/workload_identity_interop_test.go b/services/tests/workload_identity_interop_test.go index e6fd9dcf..b3a0d747 100644 --- a/services/tests/workload_identity_interop_test.go +++ b/services/tests/workload_identity_interop_test.go @@ -35,6 +35,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/jsonrpc" @@ -54,10 +55,12 @@ func TestWorkloadIdentity_DistinguishesEnginesAcrossProcesses(t *testing.T) { // Fake engines: 200 on any request so each inference completes promptly. fakeEngine := func(body string) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(io.Discard, r.Body) + _, copyErr := io.Copy(io.Discard, r.Body) + assert.NoError(t, copyErr) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(body)) + _, writeErr := w.Write([]byte(body)) + assert.NoError(t, writeErr) })) } ollama := fakeEngine(`{"done":true}`) @@ -107,7 +110,8 @@ func TestWorkloadIdentity_DistinguishesEnginesAcrossProcesses(t *testing.T) { strings.NewReader(`{"model":"crossengine-model","messages":[]}`), ) if err == nil { - io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) resp.Body.Close() } } @@ -127,9 +131,7 @@ func TestWorkloadIdentity_DistinguishesEnginesAcrossProcesses(t *testing.T) { continue } var p wlParams - if err := json.Unmarshal(msg.Params, &p); err != nil { - continue - } + require.NoError(t, json.Unmarshal(msg.Params, &p)) eng := p.WorkloadInfo.Engine if (eng == "ollama" || eng == "lmstudio") && p.WorkloadInfo.ID != "" { if _, seen := engineIDs[eng]; !seen { @@ -172,10 +174,12 @@ func TestWorkloadManager_RehydratesActiveWorkloadOnRestart(t *testing.T) { var releaseOnce sync.Once stopEngine := func() { releaseOnce.Do(func() { close(release) }) } ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(io.Discard, r.Body) + _, copyErr := io.Copy(io.Discard, r.Body) + assert.NoError(t, copyErr) w.Header().Set("Content-Type", "application/x-ndjson") w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{"message":{"role":"assistant","content":""},"done":false}`+"\n") + _, writeErr := io.WriteString(w, `{"message":{"role":"assistant","content":""},"done":false}`+"\n") + assert.NoError(t, writeErr) if fl, ok := w.(http.Flusher); ok { fl.Flush() } @@ -183,6 +187,7 @@ func TestWorkloadManager_RehydratesActiveWorkloadOnRestart(t *testing.T) { case <-release: case <-r.Context().Done(): } + // The request may have been canceled while the engine was held. _, _ = io.WriteString(w, `{"done":true}`+"\n") })) // Defer order matters: stopEngine() must run BEFORE ollama.Close(), because @@ -243,7 +248,8 @@ func TestWorkloadManager_RehydratesActiveWorkloadOnRestart(t *testing.T) { strings.NewReader(`{"model":"rehydrate-model","messages":[]}`), ) if err == nil { - io.Copy(io.Discard, resp.Body) + // The held stream ends during teardown or client cancellation. + _, _ = io.Copy(io.Discard, resp.Body) resp.Body.Close() } }() @@ -287,10 +293,12 @@ func TestWorkloadManager_RehydratesRecentTerminalOnRestart(t *testing.T) { // Fake Ollama that completes immediately, so the workload reaches a terminal // (completed) state right away. ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(io.Discard, r.Body) + _, copyErr := io.Copy(io.Discard, r.Body) + assert.NoError(t, copyErr) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"done":true}`)) + _, writeErr := w.Write([]byte(`{"done":true}`)) + assert.NoError(t, writeErr) })) defer ollama.Close() ollamaPort := portOfURL(t, ollama.URL) @@ -335,7 +343,8 @@ func TestWorkloadManager_RehydratesRecentTerminalOnRestart(t *testing.T) { strings.NewReader(`{"model":"rehydrate-term-model","messages":[]}`), ) if err == nil { - io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) resp.Body.Close() } @@ -377,9 +386,7 @@ func waitStubPeerWorkload(t *testing.T, received <-chan jsonrpc.Message, model, continue } var p wlParams - if json.Unmarshal(msg.Params, &p) != nil { - continue - } + require.NoError(t, json.Unmarshal(msg.Params, &p)) if p.WorkloadInfo.Model == model && p.WorkloadInfo.State == state { return } diff --git a/services/tests/workload_interop_test.go b/services/tests/workload_interop_test.go index a4876964..6a687b92 100644 --- a/services/tests/workload_interop_test.go +++ b/services/tests/workload_interop_test.go @@ -95,7 +95,7 @@ func startBrokerProcInCluster(t *testing.T, clusterDir string, args ...string) ( require.NoError(t, cmd.Start(), "start broker") t.Logf("broker started: pid=%d", cmd.Process.Pid) - ch := startMsgReader(stdoutPipe) + ch := startMsgReader(t, stdoutPipe) return stdinPipe, ch, func() { stdinPipe.Close() done := make(chan error, 1) @@ -199,9 +199,7 @@ func TestWorkloadRelay_SuppressesOutOfOrderStateUpdates(t *testing.T) { continue } var p wlParams - if json.Unmarshal(msg.Params, &p) != nil { - continue - } + require.NoError(t, json.Unmarshal(msg.Params, &p)) require.False(t, p.WorkloadInfo.ID == "ooo-1" && p.WorkloadInfo.State == "running", "stale running after failed was re-emitted to the client — reordering not suppressed") case <-deadline: draining = false @@ -316,15 +314,20 @@ func TestWorkloadRelay_FailsActiveWorkWhenNodeDisappears(t *testing.T) { func TestWorkloadManager_BroadcastsLocalEventsToPeers(t *testing.T) { // Fake Ollama upstream the proxy forwards to (200 on any request). ollama := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.Copy(io.Discard, r.Body) + _, copyErr := io.Copy(io.Discard, r.Body) + assert.NoError(t, copyErr) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"done":true}`)) + _, writeErr := w.Write([]byte(`{"done":true}`)) + assert.NoError(t, writeErr) })) defer ollama.Close() - ou, _ := url.Parse(ollama.URL) - _, ollamaPortStr, _ := net.SplitHostPort(ou.Host) - ollamaPort, _ := strconv.Atoi(ollamaPortStr) + ou, err := url.Parse(ollama.URL) + require.NoError(t, err) + _, ollamaPortStr, err := net.SplitHostPort(ou.Host) + require.NoError(t, err) + ollamaPort, err := strconv.Atoi(ollamaPortStr) + require.NoError(t, err) // Stub peer: a real cluster peer (cross-pinned with the node under test) that // records the workload broadcasts the manager fans out to it. It has to be a @@ -368,7 +371,8 @@ func TestWorkloadManager_BroadcastsLocalEventsToPeers(t *testing.T) { strings.NewReader(`{"model":"test-model","messages":[]}`), ) if err == nil { - io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) resp.Body.Close() } } @@ -414,7 +418,8 @@ func postPeerEvent(t *testing.T, client *http.Client, endpoint, body string, tim for { resp, err := client.Post(endpoint, "application/json", strings.NewReader(body)) if err == nil { - io.Copy(io.Discard, resp.Body) + _, copyErr := io.Copy(io.Discard, resp.Body) + require.NoError(t, copyErr) resp.Body.Close() if resp.StatusCode == http.StatusOK { return @@ -476,7 +481,8 @@ func waitEngineProxyReady(t *testing.T, namespace string, stdin io.Writer, msgs continue } var id int64 - if json.Unmarshal(*msg.ID, &id) != nil || !pending[id] { + require.NoError(t, json.Unmarshal(*msg.ID, &id)) + if !pending[id] { continue } delete(pending, id) @@ -484,7 +490,8 @@ func waitEngineProxyReady(t *testing.T, namespace string, stdin io.Writer, msgs Ready bool `json:"ready"` Port int `json:"port"` } - if json.Unmarshal(msg.Result, &st) == nil && st.Ready { + require.NoError(t, json.Unmarshal(msg.Result, &st)) + if st.Ready { return st.Port } case <-tick.C: From c0878a5c1c94c1c89aae18ba9486c449b89d8411 Mon Sep 17 00:00:00 2001 From: Kaylee Lubick Date: Tue, 20 Oct 2026 14:32:13 -0400 Subject: [PATCH 3/4] Explain complex Go test cases Signed-off-by: Kaylee Lubick --- scripts/inference-dispatcher/dispatcher_test.go | 4 ++++ .../admission_migration_test.go | 2 ++ services/nvpair-engine-manager/executor_test.go | 8 ++++++++ services/nvpair-engine-manager/models_test.go | 2 ++ services/nvpair-engine-manager/pull_llamacpp_test.go | 2 ++ services/nvpair-engine-manager/pull_test.go | 6 ++++++ services/nvpair-engine-manager/settings_test.go | 4 ++++ .../nvpair-engine-manager/settingsremote_test.go | 4 ++++ services/nvpair-job-scheduler/telemetry_test.go | 2 ++ .../nvpair-node-scanner/node_info_outage_test.go | 2 ++ services/nvpair-node-scanner/telemetry_test.go | 12 ++++++++++++ services/nvpair-proxy/alias_test.go | 2 ++ services/nvpair-proxy/cors_test.go | 4 ++++ services/nvpair-tui/ui/toast_test.go | 2 ++ services/nvpair-ui-broker/broker_lifecycle_test.go | 2 ++ services/nvpair-ui-broker/brokertest_test.go | 2 ++ services/nvpair-ui-broker/lmstudioport_test.go | 10 ++++++++++ services/nvpair-ui-broker/scheduler_test.go | 2 ++ services/shared/cors/cors_test.go | 2 ++ services/tests/llamacpp_interop_test.go | 2 ++ services/tests/model_routing_interop_test.go | 2 ++ 21 files changed, 78 insertions(+) diff --git a/scripts/inference-dispatcher/dispatcher_test.go b/scripts/inference-dispatcher/dispatcher_test.go index 7dab4f3e..4f8e3b87 100644 --- a/scripts/inference-dispatcher/dispatcher_test.go +++ b/scripts/inference-dispatcher/dispatcher_test.go @@ -51,6 +51,8 @@ func configuredTestClient(t *testing.T, args []string, baseURL string, stderr *b return cfg, client } +// The inventory puts an embedding model first and chat models out of order. +// Default selection must find a generation-capable model rather than use list order. func TestOmittedModel_SelectsAvailableGenerationModel(t *testing.T) { var receivedModel string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -145,6 +147,8 @@ func TestLMStudio_FallsBackToOpenAIInventory(t *testing.T) { assert.Contains(t, stdout.String(), "live-model", "selected model missing from output") } +// llama.cpp exposes OpenAI inventory and chat routes rather than Ollama's native +// API. Auto-selection and dispatch must agree on that protocol. func TestLlamaCPP_UsesOpenAIInventoryAndChat(t *testing.T) { type observedRequest struct { method string diff --git a/services/nvpair-cluster-manager/admission_migration_test.go b/services/nvpair-cluster-manager/admission_migration_test.go index 9da881f3..18f6598f 100644 --- a/services/nvpair-cluster-manager/admission_migration_test.go +++ b/services/nvpair-cluster-manager/admission_migration_test.go @@ -33,6 +33,8 @@ func TestLegacyPairingInfo_MapsToFirstAdmission(t *testing.T) { require.Error(t, err, "v2 pairing info without admission epoch was accepted") } +// Old persisted pins lack admission metadata. Restart must bind them to the +// current cluster so existing members remain trusted and removable while offline. func TestRestart_MigratesLegacyPinnedMemberAdmission(t *testing.T) { dir := t.TempDir() m := testManagerAt(t, dir, 15124) diff --git a/services/nvpair-engine-manager/executor_test.go b/services/nvpair-engine-manager/executor_test.go index 669c256e..678d392c 100644 --- a/services/nvpair-engine-manager/executor_test.go +++ b/services/nvpair-engine-manager/executor_test.go @@ -76,6 +76,8 @@ func TestEngineHTTPClients_BoundResponseHeaderWait(t *testing.T) { require.Equal(t, ollamaLoadResponseHeaderTimeout, responseHeaderTimeout(t, ex.ollamaLoadClient), "Ollama load response-header timeout") } +// A cold Ollama model load can delay response headers. Only that action gets the +// longer budget, so unrelated operations still detect an unresponsive engine quickly. func TestOnlyOllamaRunModel_UsesSlowResponseHeaderBudget(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { time.Sleep(100 * time.Millisecond) @@ -229,6 +231,8 @@ func TestEngineLifecycle_DetectsStartsReportsHealthAndStops(t *testing.T) { require.False(t, st.Running, "expected stopped (%v)", st) } +// Another application can claim the port after a managed process crashes. +// Restart must recognize that owner instead of spawning a competing process. func TestCrashThenExternalService_IsAdoptedWithoutRespawn(t *testing.T) { port, err := freePort() require.NoError(t, err) @@ -595,6 +599,8 @@ func TestAdoptedEngine_ReportsObservedLiveness(t *testing.T) { require.False(t, st.Healthy, "closed external listener remained present (%v)", st) } +// The proxy also answers health requests on loopback. The identity probe must +// distinguish it from Ollama, or forwarding can route requests back into the proxy. func TestProxyFacade_CannotIdentifyAsOllama(t *testing.T) { var probes atomic.Int32 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -636,6 +642,8 @@ func TestProxyFacade_CannotIdentifyAsOllama(t *testing.T) { require.NotEqual(t, int32(0), probes.Load(), "engine identity probe did not carry the proxy sentinel") } +// A successful earlier identity check does not identify a new listener on the +// same port. Losing that identity must block adoption and installation. func TestPreviouslyAdoptedForeignReplacement_FailsClosed(t *testing.T) { var isOllama atomic.Bool isOllama.Store(true) diff --git a/services/nvpair-engine-manager/models_test.go b/services/nvpair-engine-manager/models_test.go index cc738b28..6a8c7147 100644 --- a/services/nvpair-engine-manager/models_test.go +++ b/services/nvpair-engine-manager/models_test.go @@ -16,6 +16,8 @@ import ( "github.com/stretchr/testify/require" ) +// Engines use different inventory shapes and loaded-model markers. Only rows +// matching the configured path and filter may become model identifiers. func TestExtractStrings_ReadsConfiguredResultPath(t *testing.T) { tests := []struct { name string diff --git a/services/nvpair-engine-manager/pull_llamacpp_test.go b/services/nvpair-engine-manager/pull_llamacpp_test.go index 11e67d8b..ee8e2d98 100644 --- a/services/nvpair-engine-manager/pull_llamacpp_test.go +++ b/services/nvpair-engine-manager/pull_llamacpp_test.go @@ -286,6 +286,8 @@ func TestLlamaCPPPull_PreservesOtherDownloadWhenStartIsRejected(t *testing.T) { test("negative acknowledgement", http.StatusOK, `{"success":false}`) } +// Cancellation can race an unreadable start acknowledgement. Without proof that +// this request started the download, cleanup could unload another caller's model. func TestLlamaCPPPullUnconfirmedStart_DoesNotUnload(t *testing.T) { test := func(name string, respond func(*testing.T, http.ResponseWriter, *http.Request)) { t.Run(name, func(t *testing.T) { diff --git a/services/nvpair-engine-manager/pull_test.go b/services/nvpair-engine-manager/pull_test.go index e2a413e7..bd401d36 100644 --- a/services/nvpair-engine-manager/pull_test.go +++ b/services/nvpair-engine-manager/pull_test.go @@ -88,6 +88,8 @@ func newHTTPPullTestExecutor(t *testing.T, server *httptest.Server, action Actio return executor } +// A fast download can finish as soon as the start request arrives. Subscribe +// first so its terminal event cannot be missed and leave the caller waiting. func TestPullModelLlamaCPP_SubscribesToProgressBeforeStartingDownload(t *testing.T) { const model = "owner/repo:Q4_K_M" subscribed := make(chan struct{}) @@ -169,6 +171,8 @@ func TestPullModelLlamaCPP_SubscribesToProgressBeforeStartingDownload(t *testing require.Equal(t, 100, events[1].Percent) } +// The complete download deliberately exceeds one idle interval. Advancing bytes +// must extend the deadline so slow progress is not mistaken for a stalled download. func TestPullModelOllamaAdvancingProgress_RefreshesTimeout(t *testing.T) { const ( idleTimeout = 400 * time.Millisecond @@ -216,6 +220,8 @@ func TestPullModelOllamaAdvancingProgress_RefreshesTimeout(t *testing.T) { require.Contains(t, string(result), `"status":"success"`, "pull must report terminal success") } +// The SSE stream keeps advancing beyond the initial idle deadline. Each progress +// update must renew that budget instead of imposing a total download time limit. func TestPullModelLlamaCPPAdvancingProgress_RefreshesTimeout(t *testing.T) { const ( model = "owner/repo:Q4_K_M" diff --git a/services/nvpair-engine-manager/settings_test.go b/services/nvpair-engine-manager/settings_test.go index 764e0b3e..1d0065fa 100644 --- a/services/nvpair-engine-manager/settings_test.go +++ b/services/nvpair-engine-manager/settings_test.go @@ -275,6 +275,8 @@ const ( restartsProcess ) +// Changing only the proxy port must preserve a running engine, while engine +// launch changes require a restart. Editing stopped engines must not start them. func TestConfigureLaunch_AppliesSettingsForEachEngineLifecycleState(t *testing.T) { test := func(name string, want lifecycle, edit func(*testing.T, *settings.Request)) { t.Run(name, func(t *testing.T) { @@ -443,6 +445,8 @@ func TestSettings_ValidatesAndNormalizesCORSOrigins(t *testing.T) { reject("wildcard in origin list", "http://localhost,*", "every origin") } +// Only controls declared by the manifest carry CORS meaning. Treating arbitrary +// engine flags as CORS settings could reject valid options or bypass declared checks. func TestSettingsCORS_IsOptionalAndIndependentOfEngine(t *testing.T) { setup := func(t *testing.T, corsEnv, corsFlag, prefix, suffix string) (*Executor, settings.Request, settings.Preview) { t.Helper() diff --git a/services/nvpair-engine-manager/settingsremote_test.go b/services/nvpair-engine-manager/settingsremote_test.go index ff2957f3..518c8560 100644 --- a/services/nvpair-engine-manager/settingsremote_test.go +++ b/services/nvpair-engine-manager/settingsremote_test.go @@ -87,6 +87,8 @@ func TestNotifyPeerSettings_ForwardsEverySupportedEngine(t *testing.T) { } } +// Two pinned peers observe the same mutation, then one loses its pin. Revocation +// must close its existing stream as well as reject new unauthenticated requests. func TestPairedSettings_PushesChangesAndRevokesObservers(t *testing.T) { a, certA := settingsMesh(t, "a") b, certB := settingsMesh(t, "b") @@ -243,6 +245,8 @@ func TestSettingsRemoteOnlyCORS_IsLocalOnly(t *testing.T) { } } +// Replies arrive in reverse request order to expose accidental FIFO matching. +// Cancellation must also remove its waiter so a later reply cannot retain it. func TestSettingsRelay_CorrelatesResponsesAndCleansUpCancelledRequests(t *testing.T) { sent := make(chan settings.Relay, 2) canceled := make(chan string, 1) diff --git a/services/nvpair-job-scheduler/telemetry_test.go b/services/nvpair-job-scheduler/telemetry_test.go index c332e615..54c18244 100644 --- a/services/nvpair-job-scheduler/telemetry_test.go +++ b/services/nvpair-job-scheduler/telemetry_test.go @@ -15,6 +15,8 @@ import ( "nvpair-shared/schedulerwire" ) +// Dropping to a lower pressure band requires more headroom than entering it. +// This prevents small utilization fluctuations from repeatedly changing node order. func TestTelemetryPressure_UsesBandsAndDownwardHysteresis(t *testing.T) { bands := []struct { utilization float64 diff --git a/services/nvpair-node-scanner/node_info_outage_test.go b/services/nvpair-node-scanner/node_info_outage_test.go index 75d7ae7f..6559ecd5 100644 --- a/services/nvpair-node-scanner/node_info_outage_test.go +++ b/services/nvpair-node-scanner/node_info_outage_test.go @@ -94,6 +94,8 @@ func TestNodeInfoTransport_BoundsConnectionsPerHost(t *testing.T) { require.Equal(t, 1, transport.MaxIdleConnsPerHost) } +// Two requests share one connection and each fits its own timeout. Time spent +// waiting for that connection must not consume the second request's response budget. func TestNodeInfoFetches_UseIndependentRequestBudgets(t *testing.T) { const responseDelay = 200 * time.Millisecond var requests atomic.Int32 diff --git a/services/nvpair-node-scanner/telemetry_test.go b/services/nvpair-node-scanner/telemetry_test.go index 016bafd5..897a8465 100644 --- a/services/nvpair-node-scanner/telemetry_test.go +++ b/services/nvpair-node-scanner/telemetry_test.go @@ -215,6 +215,8 @@ func TestTelemetryRetryGateRemoveRediscover_PreservesActiveClaim(t *testing.T) { require.True(t, ok, "removed attempt's late failure backed off the re-discovered node") } +// A failed peer's retry delay belongs to that peer alone. Skipping it must not +// suppress the same refresh pass's request to a healthy node. func TestRefreshTelemetry_SkipsBackedOffPeerWithoutDelayingHealthyPeer(t *testing.T) { var backedOffCalls atomic.Int32 backedOff := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -270,6 +272,8 @@ func TestRefreshTelemetry_SkipsBackedOffPeerWithoutDelayingHealthyPeer(t *testin require.Equal(t, int32(1), healthyCalls.Load(), "healthy peer received") } +// Changing peer-controlled TXT metadata does not create a new endpoint. +// It must not reset failure backoff and let an unhealthy peer trigger extra polls. func TestBrowseTXTUpdate_PreservesTelemetryRetry(t *testing.T) { var calls atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -344,6 +348,8 @@ func TestBrowseRemoval_ClearsTelemetryRetry(t *testing.T) { require.True(t, ok, "removed peer endpoint remained backed off") } +// The old request completes after discovery has installed a replacement endpoint. +// Its result must neither back off the replacement nor permit overlapping polls. func TestBrowseEndpointUpdate_WaitsForOldTelemetryBeforeClaimingReplacement(t *testing.T) { newEntered := make(chan struct{}, 1) releaseNew := make(chan struct{}, 1) @@ -435,6 +441,8 @@ func TestBrowseEndpointUpdate_WaitsForOldTelemetryBeforeClaimingReplacement(t *t require.True(t, ok, "old endpoint failure backed off the replacement endpoint") } +// One peer holds its response open across several ticks. Other peers must keep +// receiving fresh telemetry instead of waiting for that slow request to finish. func TestTelemetryLoop_KeepsHealthyNodeOnCadenceWhilePeerIsBlocked(t *testing.T) { slowStarted := make(chan struct{}, 1) slow := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { @@ -512,6 +520,8 @@ func TestTelemetryLoop_KeepsHealthyNodeOnCadenceWhilePeerIsBlocked(t *testing.T) } } +// A tick can arrive while the preceding request is still running. The node must +// keep its in-flight reservation until completion to avoid duplicate polls. func TestTelemetryLoop_DoesNotOverlapNodePolls(t *testing.T) { var active atomic.Int32 var maxActive atomic.Int32 @@ -557,6 +567,8 @@ func TestTelemetryLoop_DoesNotOverlapNodePolls(t *testing.T) { require.Equal(t, int32(1), maxActive.Load(), "maximum concurrent requests for one node") } +// Requests remain blocked across multiple ticks to expose a per-tick worker limit. +// The concurrency allowance must cover all outstanding work, including earlier ticks. func TestTelemetryLoop_BoundsConcurrentWorkAcrossTicks(t *testing.T) { const nodeCount = telemetryRefreshConcurrency + 1 entered := make(chan struct{}, nodeCount) diff --git a/services/nvpair-proxy/alias_test.go b/services/nvpair-proxy/alias_test.go index 458041a6..7a13a214 100644 --- a/services/nvpair-proxy/alias_test.go +++ b/services/nvpair-proxy/alias_test.go @@ -46,6 +46,8 @@ func TestSetLoopbackAlias_RejectsUnsafeAddresses(t *testing.T) { reject("malformed address", "not-an-address") } +// The compatibility alias must share the primary listener's routing state. +// Rebinding the primary port must not leave the alias disconnected from that router. func TestLoopbackAlias_UsesPrimaryRouterAndSurvivesPrimaryRebind(t *testing.T) { redirectConfigDir(t) rec := &recRW{} diff --git a/services/nvpair-proxy/cors_test.go b/services/nvpair-proxy/cors_test.go index 5128e830..64dac70b 100644 --- a/services/nvpair-proxy/cors_test.go +++ b/services/nvpair-proxy/cors_test.go @@ -242,6 +242,8 @@ func TestCORS_AppliesPolicyChangesToOrdinaryOptionsRequests(t *testing.T) { }) } +// Model-list policy probes can contact several engines. They must not spread a +// caller's credentials or follow a redirect to a different policy authority. func TestCORSModelList_StripsCredentialsAndDoesNotRedirect(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { var leaked atomic.Int32 @@ -286,6 +288,8 @@ func TestCORSModelList_StripsCredentialsAndDoesNotRedirect(t *testing.T) { }) } +// A paired request has already selected its destination node. Its ingress must +// preserve the local engine's CORS policy and must not route it onward again. func TestCORSPairedIngress_PreservesPolicyAndIsTerminal(t *testing.T) { forEachEngine(t, func(t *testing.T, tc engineCase) { aDir, bDir := t.TempDir(), t.TempDir() diff --git a/services/nvpair-tui/ui/toast_test.go b/services/nvpair-tui/ui/toast_test.go index bbde6958..6f466ba5 100644 --- a/services/nvpair-tui/ui/toast_test.go +++ b/services/nvpair-tui/ui/toast_test.go @@ -643,6 +643,8 @@ func TestJoining_DeclinesOtherPendingInvites(t *testing.T) { assert.Contains(t, declined, "b", "decline the two still waiting") assert.Contains(t, declined, "c", "decline the two still waiting") assert.NotContains(t, declined, "a", "decline the two still waiting") + // Recording a request happens before its reply. Wait for the commands to + // receive their replies so cleanup cannot close the pipe during a write. for range batch { select { case <-commandsDone: diff --git a/services/nvpair-ui-broker/broker_lifecycle_test.go b/services/nvpair-ui-broker/broker_lifecycle_test.go index efd64968..084e608b 100644 --- a/services/nvpair-ui-broker/broker_lifecycle_test.go +++ b/services/nvpair-ui-broker/broker_lifecycle_test.go @@ -34,6 +34,8 @@ func TestClusterManagerConfigDir_TracksBrokerClusterDir(t *testing.T) { require.Equal(t, "", (&Broker{}).clusterManagerConfigDir(), "clusterManagerConfigDir with no cluster dir") } +// Restoring engines before both proxy ports are settled can bind an engine to a +// compatibility port. Advertising must wait for the same startup decisions. func TestEngineAvailability_WaitsForBothProxyOutcomes(t *testing.T) { engineClient, engineServer := net.Pipe() defer engineClient.Close() diff --git a/services/nvpair-ui-broker/brokertest_test.go b/services/nvpair-ui-broker/brokertest_test.go index 0bf24e60..345f2e07 100644 --- a/services/nvpair-ui-broker/brokertest_test.go +++ b/services/nvpair-ui-broker/brokertest_test.go @@ -148,6 +148,8 @@ func brokerWithEngineInventory(t *testing.T, ports map[string]int) *Broker { } // assertRPCRead records malformed frames and accepts normal fixture shutdown. +// Fixture readers run in worker goroutines, where require would call FailNow +// outside the test goroutine instead of returning control to the reader. func assertRPCRead(t *testing.T, err error) bool { t.Helper() if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, net.ErrClosed) { diff --git a/services/nvpair-ui-broker/lmstudioport_test.go b/services/nvpair-ui-broker/lmstudioport_test.go index c286154d..deb24b42 100644 --- a/services/nvpair-ui-broker/lmstudioport_test.go +++ b/services/nvpair-ui-broker/lmstudioport_test.go @@ -77,6 +77,8 @@ func TestLMStudioFacadeSpec_PreservesPortPolicyAndOmitsAliasAddresses(t *testing require.Empty(t, b.lmstudioFacadeSpec().AliasAddresses, "LM Studio facade spec carried alias addresses") } +// A proxy ready event alone does not identify the local engine's port. Publish +// the reconciled engine target before releasing requests waiting on startup. func TestManagedLMStudioReady_OpensGateAndPushesBackend(t *testing.T) { engineClient, engineServer := net.Pipe() proxyClient, proxyServer := net.Pipe() @@ -142,6 +144,8 @@ func TestManagedLMStudioReady_OpensGateAndPushesBackend(t *testing.T) { } } +// Failure to reclaim the compatibility port must disable managed mode and choose +// a safe proxy port. The warning preserves the reason the expected address changed. func TestLMStudioReady_FallsBackAndWarnsForUnexpectedPort(t *testing.T) { proxyClient, proxyServer := net.Pipe() errorsClient, errorsServer := net.Pipe() @@ -602,6 +606,8 @@ func TestManagedLMStudioStaleReady_DoesNotOpenCurrentGate(t *testing.T) { requireGateStaysShut(t, b.lmstudioPortReady, "a stale proxy generation's ready") } +// The proxy can initially occupy the custom engine's port. Move the proxy first +// and retain the engine's configured port before allowing gated requests through. func TestUnmanagedLMStudioProxyCollision_RebindsBeforeGate(t *testing.T) { proxyClient, proxyServer := net.Pipe() t.Cleanup(func() { @@ -722,6 +728,8 @@ func TestUnknownLMStudioBackendFallback_SkipsUnprovenPorts(t *testing.T) { require.NotEqual(t, managedLMStudioBackendStart, fallback, "unknown-backend fallback selected unproven port") } +// An unknown engine port can coincide with the proxy's listener. Move the proxy +// before probing status so it cannot be mistaken for the engine being discovered. func TestUnknownLMStudioBackend_MovesProxyBeforeStatusProbe(t *testing.T) { proxyClient, proxyServer := net.Pipe() t.Cleanup(func() { @@ -825,6 +833,8 @@ func TestUnknownCustomBackend_KeepsRestoreGatedAfterStatusFailure(t *testing.T) } } +// A failed status call must keep restoration gated. The replacement manager must +// resolve the engine port and update the proxy before enabled engines are restored. func TestEngineManagerRespawn_ReconcilesUnknownCustomBackendBeforeRestore(t *testing.T) { proxyClient, proxyServer := net.Pipe() t.Cleanup(func() { diff --git a/services/nvpair-ui-broker/scheduler_test.go b/services/nvpair-ui-broker/scheduler_test.go index 0c0754bd..58c5af4e 100644 --- a/services/nvpair-ui-broker/scheduler_test.go +++ b/services/nvpair-ui-broker/scheduler_test.go @@ -112,6 +112,8 @@ func workloadIDFromParams(t *testing.T, params json.RawMessage) string { return env.WorkloadInfo.ID } +// Hold one delivery while two newer rankings queue. The intermediate generation +// must be dropped so delayed work cannot overwrite the latest scheduling state. func TestDeliverPriority_SkipsStaleGenerationsAndPreservesNewest(t *testing.T) { b := &Broker{} oldInput := schedulerwire.Priority{ diff --git a/services/shared/cors/cors_test.go b/services/shared/cors/cors_test.go index 68a67445..e4eec019 100644 --- a/services/shared/cors/cors_test.go +++ b/services/shared/cors/cors_test.go @@ -196,6 +196,8 @@ type roundTripFunc func(*http.Request) (*http.Response, error) func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } +// All target probes block until cancellation, exposing the shared request limit. +// A single browser preflight must not create unbounded work across cluster engines. func TestCORSFanout_BoundsConcurrentRequestsAndHonorsCancellation(t *testing.T) { var active, max atomic.Int32 started := make(chan struct{}, 20) diff --git a/services/tests/llamacpp_interop_test.go b/services/tests/llamacpp_interop_test.go index b64e6c06..22332d4b 100644 --- a/services/tests/llamacpp_interop_test.go +++ b/services/tests/llamacpp_interop_test.go @@ -58,6 +58,8 @@ func TestBrokerLlamaCPPProxySetPort_RejectsInvalidPorts(t *testing.T) { test("oversized port", `{"port":65536}`, 7303) } +// The router's native inventory determines model ownership even for the public +// OpenAI route. Preserve the full model ID rather than applying Ollama tag rules. func TestLlamaCPPFacade_UsesRouterInventoryAndExactModelIDs(t *testing.T) { const model = "org/router-model-GGUF:Q4_K_M" var modelListHits atomic.Int32 diff --git a/services/tests/model_routing_interop_test.go b/services/tests/model_routing_interop_test.go index 64e2357a..8e08da36 100644 --- a/services/tests/model_routing_interop_test.go +++ b/services/tests/model_routing_interop_test.go @@ -44,6 +44,8 @@ func newRoutingUpstream(t *testing.T, status int) *routingUpstream { return upstream } +// An eligible owner can fail while a node without the requested model remains +// reachable. Failover must stay within model owners instead of using that other node. func TestModelRouting_RejectsNodesWithoutRequestedModelAcrossProcesses(t *testing.T) { if portBusy(11435) || portBusy(1234) { t.Skip("ollama-proxy (11435) or lmstudio-proxy (1234) default port already in use; skipping") From 2f0ae02eb1c08adb062b4c1b17891bab8bfe3825 Mon Sep 17 00:00:00 2001 From: Kaylee Lubick Date: Wed, 21 Oct 2026 09:28:00 -0400 Subject: [PATCH 4/4] address feedback Signed-off-by: Kaylee Lubick --- .../self_remove_boundary_test.go | 5 +++-- .../nvpair-engine-manager/live_pound_test.go | 22 ------------------- services/tests/cluster_manager_test.go | 6 ++--- 3 files changed, 6 insertions(+), 27 deletions(-) diff --git a/services/nvpair-cluster-manager/self_remove_boundary_test.go b/services/nvpair-cluster-manager/self_remove_boundary_test.go index 0c1ea2f3..d5e4f212 100644 --- a/services/nvpair-cluster-manager/self_remove_boundary_test.go +++ b/services/nvpair-cluster-manager/self_remove_boundary_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -96,7 +97,7 @@ func TestSelfRemoveTeardown_SerializesRejoin(t *testing.T) { // The broker reflecting its still-persisted clusterId (same id) // after the node has already been unclustered by the teardown. params, err := json.Marshal(map[string]string{"clusterId": "cluster-1", "clusterFriendlyName": "Restored"}) - require.NoError(t, err) + assert.NoError(t, err) m.handleSetIdentity(&Message{Params: params}) }, func(t *testing.T, m *Manager, p1, _ pinFixture) { id, _ := m.clusterIdentity() @@ -108,7 +109,7 @@ func TestSelfRemoveTeardown_SerializesRejoin(t *testing.T) { test("full pairing commit stays atomic across teardown", func(t *testing.T, m *Manager, p2 pinFixture) { m.withClusterComposition(func() { m.setClusterIdentity("cluster-2", "Rejoined") - require.NoError(t, m.trust.Pin(&TrustedPin{ + assert.NoError(t, m.trust.Pin(&TrustedPin{ NodeUUID: p2.uuid, NodeID: "peer-2", ClusterID: "cluster-2", CertPem: p2.cert, CertFingerprint: p2.fp, PinnedAt: time.Now().UnixMilli(), })) diff --git a/services/nvpair-engine-manager/live_pound_test.go b/services/nvpair-engine-manager/live_pound_test.go index 054bd7af..8c0ebcae 100644 --- a/services/nvpair-engine-manager/live_pound_test.go +++ b/services/nvpair-engine-manager/live_pound_test.go @@ -12,7 +12,6 @@ package main import ( - "net" "os" "os/exec" "runtime" @@ -107,27 +106,6 @@ func listenAddrs(t *testing.T, port int) []string { return addrs } -func TestListenAddrs_ReturnsEmptyAfterListenerCloses(t *testing.T) { - if runtime.GOOS != "windows" { - t.Skip("listener-address assertions use Windows Get-NetTCPConnection") - } - listener, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - t.Cleanup(func() { - if err := listener.Close(); err != nil { - assert.ErrorIs(t, err, net.ErrClosed) - } - }) - _, portText, err := net.SplitHostPort(listener.Addr().String()) - require.NoError(t, err) - port, err := strconv.Atoi(portText) - require.NoError(t, err) - require.Contains(t, listenAddrs(t, port), "127.0.0.1") - - require.NoError(t, listener.Close()) - assert.Empty(t, listenAddrs(t, port)) -} - // isLoopbackOnly reports whether there is at least one listener and every // listener is a loopback address. func isLoopbackOnly(addrs []string) bool { diff --git a/services/tests/cluster_manager_test.go b/services/tests/cluster_manager_test.go index 7a6a53a0..3fcd4445 100644 --- a/services/tests/cluster_manager_test.go +++ b/services/tests/cluster_manager_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "nvpair-shared/jsonrpc" @@ -92,8 +93,7 @@ func idEquals(t *testing.T, raw *json.RawMessage, id int) bool { return false } var got int - require.NoError(t, json.Unmarshal(*raw, &got)) - return got == id + return assert.NoError(t, json.Unmarshal(*raw, &got)) && got == id } func (p *cmProc) call(method string, params any) jsonrpc.Message { @@ -125,7 +125,7 @@ func (p *cmProc) callExpectError(method string, params any) jsonrpc.Message { req["params"] = params } b, err := json.Marshal(req) - require.NoError(p.t, err, "encode %s", method) + assert.NoError(p.t, err, "encode %s", method) b = append(b, '\n') _, err = p.stdin.Write(b) require.NoError(p.t, err, "write %s", method)