utils/ctx_interrupt_test.go
Rama main · réplica de lectura del repositorio original.
package utils
import (
"testing"
"time"
)
// The CLI cancels its own root context when a `keploy mock` run ends -- that
// is how its goroutines are torn down -- so a cancelled context says nothing
// about whether a signal arrived. A caller that read the two as the same thing
// reported every recording the VS Code extension made as "interrupted", and
// the panel told the user their recording had been cut short over one that had
// just succeeded.
func TestInterruptedIsNotSetByTheCLIsOwnTeardown(t *testing.T) {
t.Cleanup(ClearInterrupted)
ClearInterrupted()
ctx := NewCtx()
if Interrupted() {
t.Fatal("a fresh context reports an interrupt that never happened")
}
// What cli/mock.go does from its defer at the end of every run.
ExecCancel()
if ctx.Err() == nil {
t.Fatal("ExecCancel did not cancel the root context")
}
if Interrupted() {
t.Fatal("the CLI's own teardown was reported as a signal")
}
// A real signal does set it.
MarkInterrupted()
if !Interrupted() {
t.Fatal("a signal was not reported")
}
}
// InterruptedAfter hears only the interrupts that come after the count a stop
// took as it began: a stop delivered twice before it (`sudo pkill -f`) is no
// interrupt of it, and a later one is.
func TestInterruptedAfterHearsOnlyLaterInterrupts(t *testing.T) {
ResetInterruptedAgain()
t.Cleanup(ResetInterruptedAgain)
heard := func(c <-chan struct{}) bool {
select {
case <-c:
return true
default:
return false
}
}
MarkInterruptedAgain() // the first stop, delivered twice
since := RepeatInterrupts()
if since != 1 {
t.Fatalf("RepeatInterrupts() = %d, want 1", since)
}
again := InterruptedAfter(since)
if heard(again) {
t.Fatal("an interrupt from before the stop was heard as one during it")
}
if !heard(InterruptedAfter(since - 1)) {
t.Fatal("an interrupt after an earlier count was not heard")
}
MarkInterruptedAgain() // during the stop
if !heard(again) {
t.Fatal("an interrupt during the stop was not heard")
}
}
// A stop offers to end at the user's next interrupt only once its first signal
// has come, and a whole notice period, as measured, after that signal was
// first seen (StopNowNotice): a single stop can arrive twice (sudo hands its
// own copy on when a signal reaches both it and keploy), and the copy comes
// with the first, never a period later. Whether the stop began with the
// signal, or on its own (the app exited, a record timer) with the signal
// coming during its wait, and even when a notice is read late, the copy ends
// nothing, and the user's interrupt after the offer ends the stop. A withdrawn
// offer ends nothing.
func TestStopNowNoticeOffersOnlyAfterACopyOfTheStopHasCome(t *testing.T) {
reset := func() { ResetInterruptedAgain(); ClearInterrupted() }
t.Cleanup(reset)
now := time.Unix(1_000_000, 0)
defer SetNoticeClockForTest(func() time.Time { return now })()
const period = 5 * time.Second
stopped := func() bool {
select {
case <-StopNow():
return true
default:
return false
}
}
t.Run("an offer before any signal is none", func(t *testing.T) {
reset()
OfferStopNow()
MarkInterrupted()
if MarkInterruptedAgain() || stopped() {
t.Fatal("an offer made before the stop's signal let its copy end the stop")
}
})
t.Run("a stop begun by a signal", func(t *testing.T) {
reset()
MarkInterrupted() // the stop
n := NewStopNowNotice(period)
if MarkInterruptedAgain() || stopped() {
t.Fatal("the stop's copy ended it before it said anything")
}
now = now.Add(period - time.Nanosecond)
if n.Next() {
t.Fatal("a notice offered before a whole period after the signal")
}
now = now.Add(time.Nanosecond)
if !n.Next() {
t.Fatal("the notice a whole period after the stop's signal does not offer")
}
if !MarkInterruptedAgain() || !stopped() {
t.Fatal("the user's interrupt after the offer did not end the stop")
}
n.Withdraw()
})
t.Run("a stop begun on its own, then interrupted, its notices read late", func(t *testing.T) {
reset()
n := NewStopNowNotice(period)
now = now.Add(period)
if n.Next() {
t.Fatal("a notice offered before any signal")
}
MarkInterrupted() // the user's stop, or a pattern kill's first copy
now = now.Add(period)
if n.Next() {
t.Fatal("the notice that first found the signal offered at once: its copy could still come")
}
if MarkInterruptedAgain() || stopped() { // the copy, after that notice
t.Fatal("the copy of the signal ended the stop")
}
now = now.Add(period / 2) // a notice read late, as its loop was busy
if n.Next() {
t.Fatal("a notice read late offered less than a whole period after the signal was seen")
}
now = now.Add(period / 2)
if !n.Next() {
t.Fatal("the notice a whole period after the signal was seen does not offer")
}
if !MarkInterruptedAgain() || !stopped() {
t.Fatal("the user's interrupt after the offer did not end the stop")
}
n.Withdraw()
})
t.Run("a withdrawn offer ends nothing", func(t *testing.T) {
reset()
MarkInterrupted()
n := NewStopNowNotice(period)
now = now.Add(period)
if !n.Next() {
t.Fatal("setup: no offer")
}
n.Withdraw()
n.Withdraw() // once only
if MarkInterruptedAgain() || stopped() {
t.Fatal("an interrupt after the stop withdrew its offer ended it")
}
if RepeatInterrupts() != 1 {
t.Fatalf("RepeatInterrupts() = %d, want every repeat counted", RepeatInterrupts())
}
})
}
// Next looks for the stop's signal before it reads the clock: a signal that
// comes between the two (the notice's goroutine preempted there) is seen at
// the next notice, never stamped as seen before it came, which would bring
// the offer, and a copy's chance to be taken for a press, under a period.
func TestStopNowNoticeNeverSeesTheSignalBeforeItCame(t *testing.T) {
ResetInterruptedAgain()
ClearInterrupted()
t.Cleanup(func() { ResetInterruptedAgain(); ClearInterrupted() })
const period = 5 * time.Second
now := time.Unix(1_000_000, 0)
armed := false
defer SetNoticeClockForTest(func() time.Time {
at := now
if armed {
// Preempted for 4 s right after reading the clock; the stop's
// signal came at the end of that.
armed = false
now = now.Add(4 * time.Second)
MarkInterrupted()
}
return at
})()
n := NewStopNowNotice(period) // a stop that began on its own
armed = true
n.Next()
if armed {
// This Next looked for the signal before it read the clock: the
// signal comes just after it.
armed = false
now = now.Add(4 * time.Second)
MarkInterrupted()
}
signalAt := now
for _, after := range []time.Duration{0, 2 * time.Second, period - time.Nanosecond} {
now = signalAt.Add(after)
if n.Next() {
t.Fatalf("offered %v after the stop's signal, under the %v period", after, period)
}
}
now = signalAt.Add(period)
if !n.Next() {
t.Fatal("no offer a whole period after the signal was seen")
}
n.Withdraw()
}