Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions sdk/go/remote/knownhosts_poll_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//go:build unix && !aix && !solaris && !illumos

package remote

import (
"os"
"path/filepath"
"testing"
"time"
)

func TestWaitLockPollDelays(t *testing.T) {
for _, tc := range []struct {
name string
wait, minimum time.Duration
}{
// A deadline shorter than the first poll must still sleep 5ms.
{"initial", time.Millisecond, 5 * time.Millisecond},
// 5+10+20+40+80+160+200ms. A zero cap busy-spins until 350ms.
{"cap", 350 * time.Millisecond, 515 * time.Millisecond},
} {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "poll.lock")
holder, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
defer holder.Close()
if err := lockFD(holder, true); err != nil {
t.Fatal(err)
}
defer unlockFD(holder)
waiter, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
t.Fatal(err)
}
defer waiter.Close()
old := pinLockWait
pinLockWait = tc.wait
defer func() { pinLockWait = old }()
start := time.Now()
err = waitLock(waiter, true, path)
elapsed := time.Since(start)
if err == nil {
t.Fatal("held lock unexpectedly acquired")
}
if elapsed < tc.minimum {
t.Fatalf("polling took %s, want at least %s", elapsed, tc.minimum)
}
})
}
}
17 changes: 17 additions & 0 deletions sdk/go/remote/options_allocation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package remote

import "testing"

// Pin the reserved extra pair before append growth conceals arithmetic errors.
func TestCIOptionsBaseAllocation(t *testing.T) {
for _, minor := range []int{4, 5, 6} {
out := CIOptions("node", "/pins/known_hosts", Version{8, minor})
want := 40
if minor >= 5 {
want = 42
}
if len(out) != want || cap(out) != 42 {
t.Errorf("8.%d: len/cap=%d/%d, want %d/42", minor, len(out), cap(out), want)
}
}
}
6 changes: 1 addition & 5 deletions sdk/go/remote/testdata/mutation-allow.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# Equivalent mutants for: bash scripts/mutation.sh sdk/go/remote --only copy.go,doc.go,exec.go,hostspec.go,knownhosts.go,knownhosts_check.go,knownhosts_compat.go,lock_unix.go,options.go,pin_option.go,resolve.go,rsync_flags.go,run.go,scan.go,ssh_allow.go,validate.go --allow remote/testdata/mutation-allow.txt
# The gate covers every non-test .go file except lock_other.go: it carries a !unix build tag (no-op stubs) and is not compiled on the linux CI host. doc.go holds no code; it is listed only so the set is exhaustive.
# Format: MUTATOR at file.go:line:col | reason. Line numbers are those of the current source; a moved line needs its entry moved (D-0253).
ARITHMETIC_BASE at knownhosts_check.go:285:13 | `5 * time.Millisecond` first poll delay in waitLock: `/` makes it 0, so the loop retries without sleeping; the same tryLockFD calls succeed or the same pinLockWait deadline refuses, so every result and error is identical; under a held lock it busy-spins on tryLockFD until the same deadline instead of sleeping
ARITHMETIC_BASE at knownhosts_check.go:298:27 | `200*time.Millisecond` poll delay cap in waitLock: `/` makes the cap 0, so later retries do not sleep; grant and deadline outcomes are identical; under a held lock it busy-spins until the same deadline instead of sleeping
ARITHMETIC_BASE at knownhosts_check.go:30:22 | unmeasurable, not equivalent: package var initialiser: Go emits no coverage block for it, so gremlins can only report NOT COVERED; TestPinLockWaitDefaultIsThirtySeconds pins the value
ARITHMETIC_BASE at options.go:115:28 | `2*len(d4Options)` in make([]string, 0, cap) is only a capacity hint; a smaller capacity makes append grow the slice, so the mutant changes only cap() of the returned slice, an allocation detail outside CIOptions' contract; the returned elements are identical
ARITHMETIC_BASE at options.go:115:43 | `+2` in the same make() capacity hint; any non-negative capacity yields the same elements and only changes cap() of the returned slice, an allocation detail outside CIOptions' contract
CONDITIONALS_BOUNDARY at scan.go:124:16 | `i < len(pairs)` vs `<=`: i starts at 1 and steps by 2 over CIOptions' flat "-o",value pairs, whose length is always even, so i never equals len(pairs) and the loop runs the same iterations
CONDITIONALS_BOUNDARY at scan.go:140:16 | `i < len(pairs)` vs `<=`: i starts at 1 and steps by 2 over CIOptions' flat "-o",value pairs, whose length is always even, so i never equals len(pairs) and the loop runs the same iterations
CONDITIONALS_BOUNDARY at ssh_allow.go:114:51 | `s[n]|0x20 <= 'z'` vs `< 'z'`: a key stopped early at 'z' is followed by neither space nor '=', so rest becomes empty and the option is refused with the same error; no sshOptionKeys entry holds a 'z', so the verdict is identical for every input
CONDITIONALS_BOUNDARY at ssh_allow.go:49:15 | `len(opts) >= 3` vs `> 3` in splitD4: with exactly -T -a -x the d4Options loop (never empty) hits i+1 >= len and returns (nil, opts), the same as the skipped branch, so splitD4's result is identical
Loading