mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
219 lines
5.6 KiB
Go
219 lines
5.6 KiB
Go
|
|
package chapters
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/AmanTahiliani/box-box/internal/models"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestDetectStructuralChapters(t *testing.T) {
|
||
|
|
chapters := Detect(nil, nil, testLaps(10, nil), 10)
|
||
|
|
|
||
|
|
if len(chapters) < 2 {
|
||
|
|
t.Fatalf("chapters len = %d, want at least start and finish", len(chapters))
|
||
|
|
}
|
||
|
|
if got := chapters[0]; got.Kind != KindStart || got.StartLap != 1 || got.EndLap != 1 {
|
||
|
|
t.Fatalf("start chapter = %+v, want L1-L1", got)
|
||
|
|
}
|
||
|
|
got := chapters[len(chapters)-1]
|
||
|
|
if got.Kind != KindFinish || got.StartLap != 9 || got.EndLap != 10 {
|
||
|
|
t.Fatalf("finish chapter = %+v, want L9-L10", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDetectReturnsEmptyWithoutData(t *testing.T) {
|
||
|
|
if chapters := Detect(nil, nil, nil, 0); len(chapters) != 0 {
|
||
|
|
t.Fatalf("chapters = %+v, want empty", chapters)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDetectFlagPeriods(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
start RaceControl
|
||
|
|
end RaceControl
|
||
|
|
wantKind string
|
||
|
|
wantTitle string
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "safety car",
|
||
|
|
start: rc(12, models.CategorySafetyCar, "", "SAFETY CAR DEPLOYED"),
|
||
|
|
end: rc(15, models.CategorySafetyCar, "", "SAFETY CAR IN THIS LAP"),
|
||
|
|
wantKind: KindSafetyCar,
|
||
|
|
wantTitle: "Safety Car (L12-L15)",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "virtual safety car",
|
||
|
|
start: rc(22, models.CategoryOther, "", "VSC DEPLOYED"),
|
||
|
|
end: rc(24, models.CategoryOther, "", "VSC ENDING"),
|
||
|
|
wantKind: KindVirtualSafetyCar,
|
||
|
|
wantTitle: "Virtual Safety Car (L22-L24)",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "red flag",
|
||
|
|
start: rc(31, models.CategoryFlag, models.FlagRed, "RED FLAG"),
|
||
|
|
end: rc(33, models.CategoryFlag, models.FlagGreen, "GREEN FLAG"),
|
||
|
|
wantKind: KindRedFlag,
|
||
|
|
wantTitle: "Red Flag (L31-L33)",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
chapters := Detect([]RaceControl{tt.start, tt.end}, nil, testLaps(40, nil), 40)
|
||
|
|
got := findKind(chapters, tt.wantKind)
|
||
|
|
if got == nil {
|
||
|
|
t.Fatalf("chapters = %+v, want %s", chapters, tt.wantKind)
|
||
|
|
}
|
||
|
|
if got.StartLap != rcLap(tt.start) || got.EndLap != rcLap(tt.end) || got.Title != tt.wantTitle {
|
||
|
|
t.Fatalf("flag chapter = %+v, want %s", *got, tt.wantTitle)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDetectPitPhaseFromPitOutLapCluster(t *testing.T) {
|
||
|
|
pitOuts := map[int][]int{
|
||
|
|
5: {1},
|
||
|
|
10: {2},
|
||
|
|
20: {3},
|
||
|
|
21: {4},
|
||
|
|
22: {5},
|
||
|
|
30: {6},
|
||
|
|
35: {7},
|
||
|
|
40: {8},
|
||
|
|
45: {9},
|
||
|
|
50: {10},
|
||
|
|
}
|
||
|
|
chapters := Detect(nil, nil, testLaps(55, pitOuts), 55)
|
||
|
|
|
||
|
|
got := findKind(chapters, KindPitPhase)
|
||
|
|
if got == nil {
|
||
|
|
t.Fatalf("chapters = %+v, want pit phase", chapters)
|
||
|
|
}
|
||
|
|
if got.StartLap != 20 || got.EndLap != 22 {
|
||
|
|
t.Fatalf("pit phase = %+v, want L20-L22", *got)
|
||
|
|
}
|
||
|
|
if len(got.DriverNumbers) != 3 || got.DriverNumbers[0] != 3 || got.DriverNumbers[2] != 5 {
|
||
|
|
t.Fatalf("pit phase drivers = %v, want [3 4 5]", got.DriverNumbers)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDetectDecisiveSwingPersistsToFinish(t *testing.T) {
|
||
|
|
positions := []PositionSample{
|
||
|
|
pos(1, 1, 1),
|
||
|
|
pos(1, 16, 4),
|
||
|
|
pos(1, 55, 3),
|
||
|
|
pos(6, 16, 3),
|
||
|
|
pos(6, 55, 4),
|
||
|
|
pos(8, 44, 5),
|
||
|
|
pos(8, 63, 6),
|
||
|
|
pos(10, 44, 6),
|
||
|
|
}
|
||
|
|
|
||
|
|
chapters := Detect(nil, positions, testLaps(12, nil), 12)
|
||
|
|
got := findKind(chapters, KindDecisiveSwing)
|
||
|
|
if got == nil {
|
||
|
|
t.Fatalf("chapters = %+v, want decisive swing", chapters)
|
||
|
|
}
|
||
|
|
if got.StartLap != 6 || got.EndLap != 6 {
|
||
|
|
t.Fatalf("swing lap = %+v, want L6", *got)
|
||
|
|
}
|
||
|
|
if len(got.DriverNumbers) != 2 || got.DriverNumbers[0] != 16 || got.DriverNumbers[1] != 55 {
|
||
|
|
t.Fatalf("swing drivers = %v, want [16 55]", got.DriverNumbers)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDetectFlagPeriodsWinOverConflictingChapters(t *testing.T) {
|
||
|
|
pitOuts := map[int][]int{
|
||
|
|
12: {1, 2},
|
||
|
|
13: {3, 4},
|
||
|
|
14: {5, 6},
|
||
|
|
}
|
||
|
|
rcs := []RaceControl{
|
||
|
|
rc(12, models.CategorySafetyCar, "", "SAFETY CAR DEPLOYED"),
|
||
|
|
rc(15, models.CategorySafetyCar, "", "SAFETY CAR IN THIS LAP"),
|
||
|
|
}
|
||
|
|
|
||
|
|
chapters := Detect(rcs, nil, testLaps(20, pitOuts), 20)
|
||
|
|
if got := findKind(chapters, KindSafetyCar); got == nil || got.StartLap != 12 || got.EndLap != 15 {
|
||
|
|
t.Fatalf("chapters = %+v, want safety car L12-L15", chapters)
|
||
|
|
}
|
||
|
|
if got := findKind(chapters, KindPitPhase); got != nil {
|
||
|
|
t.Fatalf("pit phase = %+v, want omitted under safety car", *got)
|
||
|
|
}
|
||
|
|
for i := 1; i < len(chapters); i++ {
|
||
|
|
if chapters[i].StartLap <= chapters[i-1].EndLap {
|
||
|
|
t.Fatalf("chapters overlap at %d: %+v then %+v", i, chapters[i-1], chapters[i])
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func rcLap(r RaceControl) int {
|
||
|
|
if r.LapNumber == nil {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
return *r.LapNumber
|
||
|
|
}
|
||
|
|
|
||
|
|
func findKind(chapters []Chapter, kind string) *Chapter {
|
||
|
|
for i := range chapters {
|
||
|
|
if chapters[i].Kind == kind {
|
||
|
|
return &chapters[i]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func testLaps(total int, pitOuts map[int][]int) []Lap {
|
||
|
|
var laps []Lap
|
||
|
|
for lap := 1; lap <= total; lap++ {
|
||
|
|
drivers := []int{1}
|
||
|
|
if pitDrivers := pitOuts[lap]; len(pitDrivers) > 0 {
|
||
|
|
drivers = pitDrivers
|
||
|
|
}
|
||
|
|
for _, driver := range drivers {
|
||
|
|
laps = append(laps, Lap{
|
||
|
|
DriverNumber: driver,
|
||
|
|
LapNumber: lap,
|
||
|
|
DateStart: lapTime(lap),
|
||
|
|
IsPitOutLap: containsDriver(pitOuts[lap], driver),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return laps
|
||
|
|
}
|
||
|
|
|
||
|
|
func rc(lap int, category models.RaceControlCategory, flag models.Flag, message string) RaceControl {
|
||
|
|
return RaceControl{
|
||
|
|
Category: category,
|
||
|
|
Flag: flag,
|
||
|
|
Message: message,
|
||
|
|
LapNumber: &lap,
|
||
|
|
Date: lapTime(lap),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func pos(lap int, driver int, position int) PositionSample {
|
||
|
|
return PositionSample{
|
||
|
|
DriverNumber: driver,
|
||
|
|
Position: position,
|
||
|
|
Date: lapTime(lap),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func lapTime(lap int) string {
|
||
|
|
minute := lap - 1
|
||
|
|
return fmt.Sprintf("2025-05-25T13:%02d:00Z", minute)
|
||
|
|
}
|
||
|
|
|
||
|
|
func containsDriver(drivers []int, driver int) bool {
|
||
|
|
for _, candidate := range drivers {
|
||
|
|
if candidate == driver {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|