124 lines
2.8 KiB
Go
124 lines
2.8 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"sort"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"peoplesgrocers.com/code/oss/lseq/golang"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Operation struct {
|
||
|
|
Before *int `json:"before,omitempty"`
|
||
|
|
After *int `json:"after,omitempty"`
|
||
|
|
Expected string `json:"expected"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type Scenario struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Description string `json:"description"`
|
||
|
|
Seed int `json:"seed"`
|
||
|
|
Init []string `json:"init"`
|
||
|
|
RNG []float64 `json:"rng"`
|
||
|
|
Operations []Operation `json:"operations"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func createMockRandom(values []float64) func() float64 {
|
||
|
|
index := 0
|
||
|
|
return func() float64 {
|
||
|
|
if index >= len(values) {
|
||
|
|
panic("ran out of random values")
|
||
|
|
}
|
||
|
|
v := values[index]
|
||
|
|
index++
|
||
|
|
return v
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestConformance(t *testing.T) {
|
||
|
|
testDataDir := "../../genfiles"
|
||
|
|
entries, err := os.ReadDir(testDataDir)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to read test data directory: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var files []string
|
||
|
|
for _, entry := range entries {
|
||
|
|
if strings.HasSuffix(entry.Name(), ".scenario.json") {
|
||
|
|
files = append(files, entry.Name())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sort.Strings(files)
|
||
|
|
|
||
|
|
for _, file := range files {
|
||
|
|
filePath := filepath.Join(testDataDir, file)
|
||
|
|
data, err := os.ReadFile(filePath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("Failed to read %s: %v", file, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var scenario Scenario
|
||
|
|
if err := json.Unmarshal(data, &scenario); err != nil {
|
||
|
|
t.Fatalf("Failed to parse %s: %v", file, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
t.Run(scenario.Name, func(t *testing.T) {
|
||
|
|
mockRandom := createMockRandom(scenario.RNG)
|
||
|
|
l := lseq.NewLSEQ(mockRandom)
|
||
|
|
state := make([]string, len(scenario.Init))
|
||
|
|
copy(state, scenario.Init)
|
||
|
|
|
||
|
|
for i, op := range scenario.Operations {
|
||
|
|
var beforeKey, afterKey *string
|
||
|
|
var insertIdx int
|
||
|
|
|
||
|
|
if op.Before != nil {
|
||
|
|
// Insert before index X
|
||
|
|
idx := *op.Before
|
||
|
|
if idx < 0 {
|
||
|
|
idx = len(state) + idx
|
||
|
|
}
|
||
|
|
if idx > 0 {
|
||
|
|
beforeKey = &state[idx-1]
|
||
|
|
}
|
||
|
|
if idx < len(state) {
|
||
|
|
afterKey = &state[idx]
|
||
|
|
}
|
||
|
|
insertIdx = idx
|
||
|
|
} else if op.After != nil {
|
||
|
|
// Insert after index X
|
||
|
|
idx := *op.After
|
||
|
|
if idx < 0 {
|
||
|
|
idx = len(state) + idx
|
||
|
|
}
|
||
|
|
if idx >= 0 && idx < len(state) {
|
||
|
|
beforeKey = &state[idx]
|
||
|
|
}
|
||
|
|
if idx+1 < len(state) {
|
||
|
|
afterKey = &state[idx+1]
|
||
|
|
}
|
||
|
|
insertIdx = idx + 1
|
||
|
|
} else {
|
||
|
|
// Neither specified - insert at end
|
||
|
|
if len(state) > 0 {
|
||
|
|
beforeKey = &state[len(state)-1]
|
||
|
|
}
|
||
|
|
insertIdx = len(state)
|
||
|
|
}
|
||
|
|
|
||
|
|
result := l.Alloc(beforeKey, afterKey)
|
||
|
|
|
||
|
|
if result != op.Expected {
|
||
|
|
t.Errorf("op %d: alloc(%v, %v) = %q, want %q",
|
||
|
|
i, beforeKey, afterKey, result, op.Expected)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Insert result into state
|
||
|
|
state = append(state[:insertIdx], append([]string{result}, state[insertIdx:]...)...)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|