Ctrl K
ComunidadGuías…

pkg/models/window_schedule_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package models

import (
	"testing"
	"time"
)

var sched0 = time.Date(2026, 9, 30, 10, 0, 0, 0, time.UTC)

func at(ms int) time.Time { return sched0.Add(time.Duration(ms) * time.Millisecond) }

// A long early window (test-2, 200..5000) that later tests overlap, as in the
// production recording where 814 MESSAGEs sit in one such window. Deliberately
// given out of Start order: the constructor sorts.
func overlapSchedule() *WindowSchedule {
	return NewWindowSchedule([]TestWindow{
		{TestCase: "test-3", Start: at(1000), End: at(1100)},
		{TestCase: "test-1", Start: at(100), End: at(150)},
		{TestCase: "test-2", Start: at(200), End: at(5000)},
		{TestCase: "test-4", Start: at(6000), End: at(6100)},
		{TestCase: "bad-zero", End: at(7000)},                   // dropped: no start
		{TestCase: "test-5", Start: at(8000), End: at(7000)},    // inverted: clamped
		{TestCase: "test-6", Start: at(9000), End: time.Time{}}, // zero end: clamped
	})
}

func TestWindowScheduleSortsAndCleans(t *testing.T) {
	s := overlapSchedule()
	want := []string{"test-1", "test-2", "test-3", "test-4", "test-5", "test-6"}
	if s.Len() != len(want) {
		t.Fatalf("Len = %d, want %d", s.Len(), len(want))
	}
	for i, name := range want {
		if got := s.Window(i).TestCase; got != name {
			t.Fatalf("window %d = %s, want %s", i, got, name)
		}
	}
	if !s.Window(4).End.Equal(at(8000)) || !s.Window(5).End.Equal(at(9000)) {
		t.Fatalf("inverted/zero ends not clamped to start: %v %v", s.Window(4).End, s.Window(5).End)
	}
}

func TestWindowScheduleReleaseIndex(t *testing.T) {
	s := overlapSchedule()
	for _, tc := range []struct {
		name string
		t    time.Time
		want int
	}{
		{"before every window is startup", at(50), -1},
		{"inside test-1", at(120), 0},
		{"gap after test-1 belongs to test-1", at(170), 0},
		{"inside the long window only", at(300), 1},
		// test-3 [1000,1100] also contains 1050, but the long window is the
		// FIRST containing one. Releasing at test-3 would make test-2's own
		// handler wait on it.
		{"inside the long window AND test-3 releases at the long window", at(1050), 1},
		{"after test-3 but still inside the long window", at(4000), 1},
		{"gap after the long window belongs to the latest started (test-3)", at(5500), 2},
		{"exact start of test-4", at(6000), 3},
		{"exact end of test-4", at(6100), 3},
		{"after the last window", at(20000), 5},
	} {
		if got := s.ReleaseIndex(tc.t); got != tc.want {
			t.Errorf("%s: ReleaseIndex = %d, want %d", tc.name, got, tc.want)
		}
	}
}

func TestWindowScheduleReleasedAndHorizon(t *testing.T) {
	s := overlapSchedule()
	// While test-3 is current (index 2): everything up to the long window's
	// end is released (maxEnd 5000 beats test-4's start - 1ns), and the gap
	// after it too, until test-4 starts.
	cur := at(1000)
	h, ok := s.ReleaseHorizon(cur)
	if !ok || !h.Equal(at(6000).Add(-time.Nanosecond)) {
		t.Fatalf("horizon at test-3 = %v,%v; want test-4 start - 1ns", h, ok)
	}
	for _, tc := range []struct {
		t    time.Time
		want bool
	}{
		{at(50), true}, {at(4999), true}, {at(5999), true}, {at(6000), false}, {at(9500), false},
	} {
		if got := s.Released(tc.t, cur); got != tc.want {
			t.Errorf("Released(%v) at test-3 = %v, want %v", tc.t.Sub(sched0), got, tc.want)
		}
	}
	// While test-1 is current: the long window has not started, so its
	// traffic waits. Horizon = test-2 start - 1ns.
	h, ok = s.ReleaseHorizon(at(100))
	if !ok || !h.Equal(at(200).Add(-time.Nanosecond)) {
		t.Fatalf("horizon at test-1 = %v,%v; want test-2 start - 1ns", h, ok)
	}
	if s.Released(at(300), at(100)) {
		t.Fatal("the long window's traffic was released while test-1 ran")
	}
	// A current start before every window (staging) releases only startup.
	h, ok = s.ReleaseHorizon(at(10))
	if !ok || !h.Equal(at(100).Add(-time.Nanosecond)) {
		t.Fatalf("horizon before test-1 = %v,%v", h, ok)
	}
	// The last window's horizon is unbounded.
	if _, ok := s.ReleaseHorizon(at(9000)); ok {
		t.Fatal("the last window must have an unbounded horizon")
	}
	// A current start that falls between recorded windows (an unselected test
	// was skipped) counts as the latest window started by then.
	if !s.Released(at(5500), at(5800)) {
		t.Fatal("the gap after test-3 must be released once the replay is past test-3")
	}
}

// The horizon must agree with Released at every boundary; a brute-force walk
// over a fine grid catches an off-by-one in either.
func TestWindowScheduleHorizonMatchesReleased(t *testing.T) {
	s := overlapSchedule()
	for curMs := 0; curMs <= 10000; curMs += 50 {
		cur := at(curMs)
		h, bounded := s.ReleaseHorizon(cur)
		for tMs := 0; tMs <= 10000; tMs += 25 {
			tt := at(tMs)
			want := s.Released(tt, cur)
			got := !bounded || !tt.After(h)
			if got != want {
				t.Fatalf("cur=%dms t=%dms: horizon says %v, Released says %v (horizon %v)", curMs, tMs, got, want, h.Sub(sched0))
			}
		}
	}
}

func TestWindowScheduleNilAndEmpty(t *testing.T) {
	var s *WindowSchedule
	if s.Len() != 0 || s.CurrentIndex(at(1)) != -1 || s.ReleaseIndex(at(1)) != -1 {
		t.Fatal("nil schedule must be empty")
	}
	if !s.Released(at(100), at(0)) {
		t.Fatal("with no schedule everything is released")
	}
	if _, ok := s.ReleaseHorizon(at(0)); ok {
		t.Fatal("with no schedule the horizon is unbounded")
	}
	e := NewWindowSchedule(nil)
	if e.Len() != 0 || !e.Released(at(100), at(0)) {
		t.Fatal("empty schedule must behave as nil")
	}
}

// The gap before a window runs from the latest end of the windows that start
// before it to its start, exclusive at both ends; the first window has none
// (what precedes it is start-up traffic), and neither has a window an earlier
// one reaches, nor a start no window has. A call sent in a gap and answered at
// or after the next window's start was in flight when that test began; one
// sent and answered in the gap, one sent inside an earlier window, and one
// sent before the gap's start are not.
func TestWindowScheduleGapBeforeAndInFlight(t *testing.T) {
	s := NewWindowSchedule([]TestWindow{
		{TestCase: "test-1", Start: at(1000), End: at(1100)},
		{TestCase: "test-2", Start: at(2000), End: at(2100)},
		{TestCase: "long", Start: at(3000), End: at(5000)},
		{TestCase: "inside", Start: at(4000), End: at(4100)},
	})
	if from, ok := s.GapBefore(at(2000)); !ok || !from.Equal(at(1100)) {
		t.Fatalf("gap before test-2 from %v ok=%v; want from test-1's end", from, ok)
	}
	for _, c := range []struct {
		name  string
		start time.Time
	}{{"the first window", at(1000)}, {"a window an earlier one reaches", at(4000)}, {"a start no window has", at(2500)}} {
		if _, ok := s.GapBefore(c.start); ok {
			t.Fatalf("%s has a gap", c.name)
		}
	}
	together := NewWindowSchedule([]TestWindow{
		{TestCase: "test-1", Start: at(1000), End: at(1100)},
		{TestCase: "b", Start: at(2000), End: at(2100)},
		{TestCase: "c", Start: at(2000), End: at(2200)},
	})
	if _, ok := together.GapBefore(at(2000)); ok || together.InFlightInto(at(1500), at(2050), at(2000)) {
		t.Fatal("two windows that start together have a gap: which test a call in flight then is cannot be told")
	}
	var nilSched *WindowSchedule
	if _, ok := nilSched.GapBefore(at(2000)); ok || nilSched.InFlightInto(at(1500), at(2050), at(2000)) {
		t.Fatal("no schedule has a gap")
	}
	for _, c := range []struct {
		name     string
		req, res int
		want     bool
	}{
		{"sent in the gap, answered in the window", 1500, 2050, true},
		{"sent in the gap, answered at its start", 1500, 2000, true},
		{"sent in the gap, answered after the window", 1500, 2500, true},
		{"sent and answered in the gap", 1500, 1900, false},
		{"sent at the gap's start (test-1's end)", 1100, 2050, false},
		{"sent inside test-1", 1050, 2050, false},
		{"sent at the window's start", 2000, 2050, false},
	} {
		if got := s.InFlightInto(at(c.req), at(c.res), at(2000)); got != c.want {
			t.Errorf("%s: InFlightInto = %v, want %v", c.name, got, c.want)
		}
	}
	if s.InFlightInto(at(1500), time.Time{}, at(2000)) {
		t.Error("a call with no answer time was in flight")
	}
}

// After is past every window's End, the latest End and not the last-started
// window's: a long early window that a later, shorter one starts inside still
// holds what was recorded before its own End. A moment at that End is the
// window's (Contains), not after it. An empty schedule and a zero moment tell
// no teardown.
func TestWindowScheduleAfterIsPastEveryWindowsEnd(t *testing.T) {
	s := NewWindowSchedule([]TestWindow{
		{TestCase: "test-2", Start: at(200), End: at(300)},
		{TestCase: "test-1", Start: at(100), End: at(5000)},
	})
	for _, c := range []struct {
		ms   int
		want bool
	}{{50, false}, {150, false}, {250, false}, {400, false}, {5000, false}, {5001, true}, {9000, true}} {
		if got := s.After(at(c.ms)); got != c.want {
			t.Fatalf("After(%d) = %v, want %v", c.ms, got, c.want)
		}
	}
	if s.After(time.Time{}) {
		t.Fatal("a zero moment is after the windows")
	}
	var none *WindowSchedule
	if none.After(at(9000)) || NewWindowSchedule(nil).After(at(9000)) {
		t.Fatal("with no schedule a moment is after the windows")
	}
}

// A test merged into a window no test names (TestWindow.Tests) runs in that
// window (IndexOfTest), and has no window of its own (Of).
func TestWindowSchedule_AMergedTestRunsInItsMergedWindow(t *testing.T) {
	t0 := time.Date(2026, 11, 6, 0, 0, 0, 0, time.UTC)
	s := NewWindowSchedule([]TestWindow{
		{TestCase: "c", Start: t0.Add(20 * time.Second), End: t0.Add(30 * time.Second)},
		{Start: t0, End: t0.Add(12 * time.Second), Tests: []string{"a", "b", ""}},
	})
	// An empty name names no test, though a merged window lists one.
	for _, tc := range []struct {
		test string
		i    int
		ok   bool
	}{{"a", 0, true}, {"b", 0, true}, {"c", 1, true}, {"d", 0, false}, {"", 0, false}} {
		if i, ok := s.IndexOfTest(tc.test); i != tc.i || ok != tc.ok {
			t.Errorf("IndexOfTest(%q) = %d, %v; want %d, %v", tc.test, i, ok, tc.i, tc.ok)
		}
	}
	if _, ok := s.Of("a"); ok {
		t.Error("a merged test has a window of its own")
	}
	// A test listed in two merged windows runs in the first in Start order.
	twice := NewWindowSchedule([]TestWindow{
		{Start: t0.Add(40 * time.Second), End: t0.Add(50 * time.Second), Tests: []string{"a", "e"}},
		{Start: t0, End: t0.Add(12 * time.Second), Tests: []string{"a", "b"}},
	})
	if i, ok := twice.IndexOfTest("a"); i != 0 || !ok {
		t.Errorf("a test in two merged windows: IndexOfTest = %d, %v; want the first, 0", i, ok)
	}
	if w, ok := s.Of("c"); !ok || w.TestCase != "c" {
		t.Errorf("Of(c) = %+v, %v", w, ok)
	}
}

// keploy mock replay's windows do not end at their tests' ends: for the run's
// place each runs up to the next one's start and the last has no end, in
// Start order, a merged window keeping its tests. An End past the next start
// (windows that were not merged) stays. None stays none.
func TestRunWindowsRunUpToTheNextStart(t *testing.T) {
	t0 := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
	at := func(s int) time.Time { return t0.Add(time.Duration(s) * time.Second) }
	in := []TestWindow{
		{TestCase: "c", Start: at(30), End: at(31)},
		{Tests: []string{"a", "b"}, Start: at(10), End: at(12)},
		{TestCase: "d", Start: at(20), End: at(35)},
	}
	got := RunWindows(in)
	if in[0].TestCase != "c" || !in[0].End.Equal(at(31)) {
		t.Fatalf("the windows it was given changed: %v", in)
	}
	if len(got) != 3 || len(got[0].Tests) != 2 || got[1].TestCase != "d" || got[2].TestCase != "c" {
		t.Fatalf("windows %v, want them in Start order", got)
	}
	if want := at(20).Add(-time.Nanosecond); !got[0].End.Equal(want) {
		t.Errorf("first ends at %v, want %v", got[0].End, want)
	}
	if !got[1].End.Equal(at(35)) {
		t.Errorf("an End past the next start became %v", got[1].End)
	}
	if !got[2].End.After(at(1 << 32)) {
		t.Errorf("the last ends at %v, want past any recording", got[2].End)
	}
	if got := RunWindows(nil); len(got) != 0 {
		t.Errorf("none: %v", got)
	}
}