diff --git a/internal/utils/multiindex_test.go b/internal/utils/multiindex_test.go new file mode 100644 index 0000000..b7daedd --- /dev/null +++ b/internal/utils/multiindex_test.go @@ -0,0 +1,202 @@ +package utils + +import ( + "testing" +) + +type TestUser struct { + ID string + Name string + Email string +} + +func TestMultiIndex_AddAndFind(t *testing.T) { + idx := NewMultiIndex[TestUser](2) + + user := TestUser{"1", "John Doe", "john@example.com"} + idx.AddItem(user, [][]string{ + {"john@example.com"}, + {"john", "doe"}, + }) + + found, ok := idx.FindItem(0, []string{"john@example.com"}) + if !ok { + t.Fatal("expected to find user by email") + } + if found.ID != "1" { + t.Errorf("got ID %s, want 1", found.ID) + } + + found, ok = idx.FindItem(1, []string{"john", "doe"}) + if !ok { + t.Fatal("expected to find user by name") + } + if found.Name != "John Doe" { + t.Errorf("got Name %s, want John Doe", found.Name) + } +} + +func TestMultiIndex_NotFound(t *testing.T) { + idx := NewMultiIndex[TestUser](1) + + idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{ + {"john@example.com"}, + }) + + _, ok := idx.FindItem(0, []string{"jane@example.com"}) + if ok { + t.Error("expected not found") + } +} + +func TestMultiIndex_WrongCompositeKey(t *testing.T) { + idx := NewMultiIndex[TestUser](1) + + idx.AddItem(TestUser{"1", "John Doe", "john@example.com"}, [][]string{ + {"john", "doe"}, + }) + + _, ok := idx.FindItem(0, []string{"john", "smith"}) + if ok { + t.Error("expected not found with wrong composite key") + } + + _, ok = idx.FindItem(0, []string{"john"}) + if ok { + t.Error("expected not found with partial key") + } +} + +func TestMultiIndex_KeyCollisionHandling(t *testing.T) { + idx := NewMultiIndex[TestUser](1) + + idx.AddItem(TestUser{"1", "User1", "user1@example.com"}, [][]string{ + {"ab", "cd"}, + }) + + idx.AddItem(TestUser{"2", "User2", "user2@example.com"}, [][]string{ + {"abc", "d"}, + }) + + found, ok := idx.FindItem(0, []string{"ab", "cd"}) + if !ok || found.ID != "1" { + t.Error("collision: wrong user for ['ab', 'cd']") + } + + found, ok = idx.FindItem(0, []string{"abc", "d"}) + if !ok || found.ID != "2" { + t.Error("collision: wrong user for ['abc', 'd']") + } +} + +func TestMultiIndex_InvalidIndex(t *testing.T) { + idx := NewMultiIndex[TestUser](2) + + idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{ + {"john@example.com"}, + {"john"}, + }) + + _, ok := idx.FindItem(-1, []string{"john@example.com"}) + if ok { + t.Error("expected not found for negative index") + } + + _, ok = idx.FindItem(5, []string{"john@example.com"}) + if ok { + t.Error("expected not found for out of range index") + } +} + +func TestMultiIndex_Items(t *testing.T) { + idx := NewMultiIndex[TestUser](1) + + users := []TestUser{ + {"1", "John", "john@example.com"}, + {"2", "Jane", "jane@example.com"}, + {"3", "Bob", "bob@example.com"}, + } + + for _, u := range users { + idx.AddItem(u, [][]string{{u.Email}}) + } + + items := idx.Items() + if len(items) != 3 { + t.Fatalf("got %d items, want 3", len(items)) + } + + for i, u := range users { + if items[i].ID != u.ID { + t.Errorf("item %d: got ID %s, want %s", i, items[i].ID, u.ID) + } + } +} + +func TestMultiIndex_ItemsIsCopy(t *testing.T) { + idx := NewMultiIndex[TestUser](1) + + idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{ + {"john@example.com"}, + }) + + items := idx.Items() + items[0].Name = "Modified" + + original, _ := idx.FindItem(0, []string{"john@example.com"}) + if original.Name != "John" { + t.Error("Items() should return a copy, not affect internal state") + } +} + +func TestMultiIndex_PanicOnWrongKeysLength(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("expected panic on wrong searchKeys length") + } + }() + + idx := NewMultiIndex[TestUser](2) + idx.AddItem(TestUser{"1", "John", "john@example.com"}, [][]string{ + {"john@example.com"}, + // missing second index key + }) +} + +func TestMultiIndex_MultipleItemsSameIndex(t *testing.T) { + idx := NewMultiIndex[TestUser](2) + + idx.AddItem(TestUser{"1", "John Doe", "john@example.com"}, [][]string{ + {"john@example.com"}, + {"john", "doe"}, + }) + + idx.AddItem(TestUser{"2", "Jane Doe", "jane@example.com"}, [][]string{ + {"jane@example.com"}, + {"jane", "doe"}, + }) + + john, ok := idx.FindItem(1, []string{"john", "doe"}) + if !ok || john.ID != "1" { + t.Error("failed to find John by name") + } + + jane, ok := idx.FindItem(1, []string{"jane", "doe"}) + if !ok || jane.ID != "2" { + t.Error("failed to find Jane by name") + } +} + +func TestMultiIndex_EmptyIndex(t *testing.T) { + idx := NewMultiIndex[TestUser](1) + + _, ok := idx.FindItem(0, []string{"anything"}) + if ok { + t.Error("expected not found in empty index") + } + + items := idx.Items() + if len(items) != 0 { + t.Errorf("expected 0 items, got %d", len(items)) + } +} diff --git a/main.go b/main.go index ccf7add..19add8d 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "c65gm/internal/preproc" + "c65gm/internal/utils" "flag" "fmt" "os" @@ -35,7 +36,48 @@ const ( ShowLineView ) +func testMultiIndex() { + type User struct { + ID string + FirstName string + LastName string + Email string + } + + // 2 indexes: [0]=email only, [1]=firstname+lastname + idx := utils.NewMultiIndex[User](2) + + idx.AddItem(User{"1", "John", "Doe", "john@example.com"}, [][]string{ + {"john@example.com"}, + {"john", "doe"}, + }) + + idx.AddItem(User{"2", "Jane", "Smith", "jane@example.com"}, [][]string{ + {"jane@example.com"}, + {"jane", "smith"}, + }) + + // Search by email (index 0, single string) + user, found := idx.FindItem(0, []string{"john@example.com"}) + fmt.Println(user, found) // {1 John Doe john@example.com} true + + // Search by name (index 1, two strings) + user, found = idx.FindItem(1, []string{"jane", "smith"}) + fmt.Println(user, found) // {2 Jane Smith jane@example.com} true + + // Wrong composite key fails + user, found = idx.FindItem(1, []string{"jane", "doe"}) + fmt.Println(found) // false + + // All items + fmt.Println(idx.Items()) // [{1 John Doe john@example.com} {2 Jane Smith jane@example.com}] +} + func main() { + + testMultiIndex() + + os.Exit(1) fmt.Println("c65cm - A 6502 Cross-Compiler for the ACME Cross-Assembler.") fmt.Println("Copyright (C) 1999, 2025 Mattias Hansson. v0.7") fmt.Println("Distributed under GPL. External library github.com/armon/go-radix under MIT.")