diff --git a/commands/core/filegen.go b/commands/core/filegen.go index 2cd5ad8..89f1efb 100644 --- a/commands/core/filegen.go +++ b/commands/core/filegen.go @@ -87,7 +87,7 @@ func FileGenerator(filters ...string) GeneratorFunc { continue } - if filePrefix != "" && !hasPrefix(name, filePrefix) { + if filePrefix != "" && !HasPrefix(name, filePrefix) { continue } diff --git a/commands/core/lookup.go b/commands/core/lookup.go index ef9a201..5d2a22c 100644 --- a/commands/core/lookup.go +++ b/commands/core/lookup.go @@ -6,11 +6,11 @@ import ( "github.com/versenilvis/iris/integration/shell" ) -var shellAliases = make(map[string]string) +var ShellAliases = map[string]string{} func GetAlias(name string) (string, bool) { - target, ok := shellAliases[name] - return target, ok + val, ok := ShellAliases[name] + return val, ok } // Lookup finds matching suggestions for your input by looking at how many words you typed @@ -19,16 +19,16 @@ func GetAlias(name string) (string, bool) { // e.g. Lookup("git checkout ") -> suggests branch names via generator func Lookup(input string) []Suggestion { if shell.Current != nil { - shellAliases = shell.Current.ScanAliases() + ShellAliases = shell.Current.ScanAliases() } else { - shellAliases = make(map[string]string) + ShellAliases = make(map[string]string) } if input == "" { return nil } - tokens := tokenize(input) + tokens := Tokenize(input) if len(tokens) == 1 && tokens[0] == "" { return nil @@ -43,8 +43,8 @@ func Lookup(input string) []Suggestion { // if you have an alias in your shell config like: alias gca="git commit -a" // if the first word match it, IRIS will suggest "git commit -a" if len(tokens) > 1 { - if target, ok := shellAliases[tokens[0]]; ok { - aliasTokens := tokenize(target) + if target, ok := ShellAliases[tokens[0]]; ok { + aliasTokens := Tokenize(target) if len(aliasTokens) > 0 && aliasTokens[len(aliasTokens)-1] == "" { aliasTokens = aliasTokens[:len(aliasTokens)-1] } @@ -56,7 +56,7 @@ func Lookup(input string) []Suggestion { query := tokens[0] results := topLevelSuggestions(query) - if spec, exists := registry[query]; exists { + if spec, exists := Registry[query]; exists { hasTrailingSpace := query != "" && query[len(query)-1] == ' ' if hasTrailingSpace { @@ -82,7 +82,7 @@ func Lookup(input string) []Suggestion { } rootCmdName := tokens[0] - spec, exists := registry[rootCmdName] + spec, exists := Registry[rootCmdName] if !exists { return nil } @@ -174,7 +174,7 @@ func Lookup(input string) []Suggestion { genResults := currentGen(tokens, prefix, partial) for _, g := range genResults { - if partial != "" && !hasPrefix(g.Cmd, partial) && !strings.Contains(g.Cmd, partial) { + if partial != "" && !HasPrefix(g.Cmd, partial) && !strings.Contains(g.Cmd, partial) { continue } @@ -193,7 +193,7 @@ func Lookup(input string) []Suggestion { finalCmd = strings.TrimSpace(linePrefix) + " " + suggested } - newTokens := tokenize(finalCmd) + newTokens := Tokenize(finalCmd) if len(newTokens) > 0 { lastToken := newTokens[len(newTokens)-1] isDuplicate := false @@ -218,7 +218,7 @@ func Lookup(input string) []Suggestion { if allowMoreArgs { for _, sub := range currentSubs { - if partial == "" || hasPrefix(sub.Name, partial) { + if partial == "" || HasPrefix(sub.Name, partial) { results = append(results, Suggestion{ Cmd: prefix + " " + sub.Name, Desc: sub.Description, Icon: rootCmdName, }) @@ -234,7 +234,7 @@ func Lookup(input string) []Suggestion { } } for _, opt := range currentOpts { - if !usedOpts[opt.Name] && (partial == "" || hasPrefix(opt.Name, partial)) { + if !usedOpts[opt.Name] && (partial == "" || HasPrefix(opt.Name, partial)) { results = append(results, Suggestion{ Cmd: prefix + " " + opt.Name, Desc: opt.Description, Icon: rootCmdName, }) @@ -248,8 +248,8 @@ func Lookup(input string) []Suggestion { func topLevelSuggestions(query string) []Suggestion { results, seen := []Suggestion{}, make(map[string]bool) - for name, target := range shellAliases { - if !seen[name] && (query == "" || hasPrefix(name, query)) { + for name, target := range ShellAliases { + if !seen[name] && (query == "" || HasPrefix(name, query)) { results = append(results, Suggestion{ Cmd: target, Desc: "alias: " + name, Icon: "root", }) @@ -257,16 +257,16 @@ func topLevelSuggestions(query string) []Suggestion { } } - for name, spec := range registry { + for name, spec := range Registry { if seen[name] { continue } match := false - if query == "" || hasPrefix(name, query) { + if query == "" || HasPrefix(name, query) { match = true } else { for _, a := range spec.Aliases { - if hasPrefix(a, query) { + if HasPrefix(a, query) { match = true break } @@ -279,7 +279,7 @@ func topLevelSuggestions(query string) []Suggestion { } for name := range pathCmds { - if !seen[name] && (query == "" || hasPrefix(name, query)) { + if !seen[name] && (query == "" || HasPrefix(name, query)) { results = append(results, Suggestion{ Cmd: name, Desc: "system command", Icon: "root", }) diff --git a/commands/core/spec.go b/commands/core/spec.go index 992e4ae..8733832 100644 --- a/commands/core/spec.go +++ b/commands/core/spec.go @@ -37,10 +37,10 @@ type Suggestion struct { Icon string } -var registry = map[string]*Spec{} +var Registry = map[string]*Spec{} -// Register adds a new spec to the global registry +// Register adds a new spec to the global Registry // example: Register(&Spec{Name: "git"}) func Register(s *Spec) { - registry[s.Name] = s + Registry[s.Name] = s } diff --git a/commands/core/utils.go b/commands/core/utils.go index 9257648..3265a03 100644 --- a/commands/core/utils.go +++ b/commands/core/utils.go @@ -14,9 +14,9 @@ func debugLog(format string, a ...interface{}) { } } -// tokenize parses the input string into shell-like tokens handling quotes -// example: tokenize("git commit -m \"hello world\"") -func tokenize(s string) []string { +// SplitAliasTokens parses the input string into shell-like tokens handling quotes +// example: SplitAliasTokens("git commit -m \"hello world\"") +func Tokenize(s string) []string { tokens := []string{} var current strings.Builder inQuote := false @@ -43,8 +43,8 @@ func tokenize(s string) []string { return tokens } -// hasPrefix checks if s starts with prefix using case-insensitive matching -func hasPrefix(s, prefix string) bool { +// HasPrefix checks if s starts with prefix using case-insensitive matching +func HasPrefix(s, prefix string) bool { if len(prefix) > len(s) { return false } @@ -65,5 +65,5 @@ func hasPrefix(s, prefix string) bool { // CI = case insensitive func HasPrefixCI(s, prefix string) bool { - return hasPrefix(s, prefix) + return HasPrefix(s, prefix) } diff --git a/commands/fs/zoxide.go b/commands/fs/zoxide.go index b18d569..f3c7243 100644 --- a/commands/fs/zoxide.go +++ b/commands/fs/zoxide.go @@ -19,17 +19,17 @@ func init() { Name: "z", Description: "jump to directory", MaxArgs: 0, - Generator: zoxideGenerator(), + Generator: ZoxideGenerator(), }) core.Register(&core.Spec{ Name: "zi", Description: "jump to directory interactively", MaxArgs: 0, - Generator: zoxideGenerator(), + Generator: ZoxideGenerator(), }) } -func zoxideGenerator() core.GeneratorFunc { +func ZoxideGenerator() core.GeneratorFunc { return func(tokens []string, prefix string, partial string) []core.Suggestion { fullQuery := strings.Join(tokens[1:], " ") localSuggestions := core.FileGenerator("/")(tokens, prefix, fullQuery) diff --git a/integration/shell/adapter.go b/integration/shell/adapter.go index f15d4a9..28ef92e 100644 --- a/integration/shell/adapter.go +++ b/integration/shell/adapter.go @@ -43,7 +43,7 @@ func (b *BashAdapter) PrepareSelectSequence(selected string) []byte { return append([]byte{0x15}, []byte(selected)...) } func (b *BashAdapter) ScanAliases() map[string]string { - return scanPosixAliases([]string{".bashrc", ".bash_profile", ".bash_aliases"}) + return ScanPosixAliases([]string{".bashrc", ".bash_profile", ".bash_aliases"}) } // ZshAdapter implementation @@ -58,7 +58,7 @@ func (z *ZshAdapter) PrepareSelectSequence(selected string) []byte { return append([]byte{0x15}, []byte(selected)...) } func (z *ZshAdapter) ScanAliases() map[string]string { - return scanPosixAliases([]string{".zshrc", ".zshenv", ".zprofile"}) + return ScanPosixAliases([]string{".zshrc", ".zshenv", ".zprofile"}) } // FishAdapter implementation @@ -74,10 +74,10 @@ func (f *FishAdapter) PrepareSelectSequence(selected string) []byte { } func (f *FishAdapter) ScanAliases() map[string]string { // fish uses 'alias' command in config.fish or separate function files - return scanPosixAliases([]string{filepath.Join(".config", "fish", "config.fish")}) + return ScanPosixAliases([]string{filepath.Join(".config", "fish", "config.fish")}) } -func scanPosixAliases(files []string) map[string]string { +func ScanPosixAliases(files []string) map[string]string { aliases := make(map[string]string) home, err := os.UserHomeDir() if err != nil { @@ -105,7 +105,7 @@ func scanPosixAliases(files []string) map[string]string { continue } - pairs := splitAliasTokens(body) + pairs := SplitAliasTokens(body) for _, pair := range pairs { eqIdx := strings.IndexByte(pair, '=') if eqIdx < 0 { @@ -122,7 +122,7 @@ func scanPosixAliases(files []string) map[string]string { return aliases } -func splitAliasTokens(s string) []string { +func SplitAliasTokens(s string) []string { var tokens []string var cur strings.Builder inQuote := false diff --git a/justfile b/justfile index 2e8abad..c18eca0 100644 --- a/justfile +++ b/justfile @@ -33,3 +33,7 @@ debug: copy: @rm ~/.local/bin/iris @cp ./iris ~/.local/bin/iris +# run all tests +[group('dev')] +test: + @go test ./... -v diff --git a/root/suggestions.go b/root/suggestions.go index 5716617..a07966d 100644 --- a/root/suggestions.go +++ b/root/suggestions.go @@ -10,7 +10,7 @@ import ( // mergeResults collects and dedupes suggestions for a query and mode // example: mergeResults("git ", "spec") -func mergeResults(query string, mode string) []core.Suggestion { +func MergeResults(query string, mode string) []core.Suggestion { if query == "" && mode != "history" { debugLog("[Merge] Query empty, returning nil") return nil diff --git a/root/wrapper.go b/root/wrapper.go index 10ac72a..cd88398 100644 --- a/root/wrapper.go +++ b/root/wrapper.go @@ -207,7 +207,7 @@ func runWrapper() { continue } - results := mergeResults(query, "spec") + results := MergeResults(query, "spec") if len(results) == 0 { os.Stdout.Write([]byte(overlay.ClearAndDisable())) continue @@ -263,7 +263,7 @@ func runWrapper() { var b strings.Builder if !navCopy { debugLog("[Render] query: '%s', mode: %s", bufCopy, modeCopy) - results := mergeResults(bufCopy, modeCopy) + results := MergeResults(bufCopy, modeCopy) debugLog("[Render] results found: %d", len(results)) if len(results) == 0 { @@ -378,7 +378,7 @@ func runWrapper() { mode = "history" saveMode(mode) - results := mergeResults("", "history") + results := MergeResults("", "history") if len(results) > 0 { limit := 100 if len(results) < limit { diff --git a/tests/core/filegen_test.go b/tests/core/filegen_test.go new file mode 100644 index 0000000..518fea0 --- /dev/null +++ b/tests/core/filegen_test.go @@ -0,0 +1,61 @@ +package core_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/versenilvis/iris/commands/core" +) + +func TestFileGenerator(t *testing.T) { + // Create a temp directory structure + tmp := t.TempDir() + + os.Mkdir(filepath.Join(tmp, ".git"), 0755) + os.Mkdir(filepath.Join(tmp, "src"), 0755) + os.Mkdir(filepath.Join(tmp, "docs"), 0755) + os.WriteFile(filepath.Join(tmp, "src", "main.go"), []byte("package main"), 0644) + os.WriteFile(filepath.Join(tmp, "src", "utils.go"), []byte("package core"), 0644) + os.WriteFile(filepath.Join(tmp, "README.md"), []byte("# Readme"), 0644) + + // Helper to change CWD for test + oldWd, _ := os.Getwd() + os.Chdir(tmp) + defer os.Chdir(oldWd) + + t.Run("dirOnly shows only dirs", func(t *testing.T) { + gen := core.FileGenerator("/") + results := gen([]string{"cd", ""}, "cd ", "") + for _, r := range results { + if r.Desc != "directory" { + t.Errorf("FileGenerator(\"/\") suggested a non-directory: %s", r.Cmd) + } + } + }) + + t.Run("Filter extension", func(t *testing.T) { + gen := core.FileGenerator(".go") + results := gen([]string{"go", "src/"}, "go src/", "src/") + foundMain := false + for _, r := range results { + if r.Cmd == "src/main.go" { + foundMain = true + } + } + if !foundMain { + t.Errorf("FileGenerator(\".go\") did not suggest src/main.go") + } + }) + + t.Run("Hidden files are skipped", func(t *testing.T) { + gen := core.FileGenerator() + results := gen([]string{"ls", ""}, "ls ", "") + for _, r := range results { + if strings.HasPrefix(filepath.Base(r.Cmd), ".") { + t.Errorf("FileGenerator suggested hidden file: %s", r.Cmd) + } + } + }) +} diff --git a/tests/core/lookup_test.go b/tests/core/lookup_test.go new file mode 100644 index 0000000..46dedd3 --- /dev/null +++ b/tests/core/lookup_test.go @@ -0,0 +1,73 @@ +package core_test + +import ( + "testing" + + "github.com/versenilvis/iris/commands/core" + "github.com/versenilvis/iris/integration/shell" +) + +type mockAdapter struct { + shell.BashAdapter +} + +func (m *mockAdapter) ScanAliases() map[string]string { + return map[string]string{ + "gca": "git commit -a", + "ta": "tmux a -t", + } +} + +func TestLookup(t *testing.T) { + // Use mock adapter + shell.Current = &mockAdapter{} + + core.Register(&core.Spec{ + Name: "git", + Description: "git command", + Subcommands: []core.Subcommand{ + {Name: "commit", Description: "commit changes"}, + {Name: "remote", Description: "manage remotes", Subcommands: []core.Subcommand{ + {Name: "add", Description: "add remote"}, + }}, + }, + Options: []core.Option{ + {Name: "--verbose", Description: "verbose output"}, + }, + }) + + tests := []struct { + name string + input string + minCount int + checkCmd string + }{ + {"Top-level suggestions", "gi", 1, "git"}, + {"Subcommand suggestions", "git ", 2, "git commit"}, + {"Alias expansion", "gca", 1, "git commit -a"}, + {"Deep subcommand", "git remote ", 1, "git remote add"}, + {"Option dedup", "git --verbose -", 0, ""}, + {"Flag with value ignore", "git --output=json ", 2, "git --output=json commit"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + results := core.Lookup(tt.input) + if len(results) < tt.minCount { + t.Errorf("Lookup(%q) returned %d results; want at least %d", tt.input, len(results), tt.minCount) + } + if tt.checkCmd != "" { + found := false + for _, r := range results { + if r.Cmd == tt.checkCmd { + found = true + break + } + } + if !found { + t.Errorf("Lookup(%q) did not suggest %q", tt.input, tt.checkCmd) + } + } + }) + } +} diff --git a/tests/core/utils_test.go b/tests/core/utils_test.go new file mode 100644 index 0000000..8307753 --- /dev/null +++ b/tests/core/utils_test.go @@ -0,0 +1,55 @@ +package core_test + +import ( + "reflect" + "testing" + + "github.com/versenilvis/iris/commands/core" +) + +func TestTokenize(t *testing.T) { + tests := []struct { + input string + expected []string + }{ + {"", []string{""}}, + {"git ", []string{"git", ""}}, + {"git add", []string{"git", "add"}}, + {"git commit -m \"hello world\"", []string{"git", "commit", "-m", "hello world"}}, + {"git commit -m \"hello", []string{"git", "commit", "-m", "hello"}}, + {"git commit -m 'hello world'", []string{"git", "commit", "-m", "hello world"}}, + {"git commit -m 'hello", []string{"git", "commit", "-m", "hello"}}, + {"ls -l \"file name\"", []string{"ls", "-l", "file name"}}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + got := core.Tokenize(tt.input) + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("Tokenize(%q) = %v; want %v", tt.input, got, tt.expected) + } + }) + } +} + +func TestHasPrefix(t *testing.T) { + tests := []struct { + s string + prefix string + want bool + }{ + {"Hello", "hel", true}, + {"Thử nghiệm", "thử", true}, + {"Iris", "Iris-Longer", false}, + {"Iris", "", true}, + {"", "a", false}, + } + + for _, tt := range tests { + t.Run(tt.s+"_"+tt.prefix, func(t *testing.T) { + if got := core.HasPrefix(tt.s, tt.prefix); got != tt.want { + t.Errorf("HasPrefix(%q, %q) = %v; want %v", tt.s, tt.prefix, got, tt.want) + } + }) + } +} diff --git a/tests/fs/zoxide_test.go b/tests/fs/zoxide_test.go new file mode 100644 index 0000000..7eb8621 --- /dev/null +++ b/tests/fs/zoxide_test.go @@ -0,0 +1,58 @@ +package fs_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/versenilvis/iris/commands/fs" +) + +func TestZoxideGenerator(t *testing.T) { + // Create a mock zoxide binary + tmp := t.TempDir() + mockZoxide := filepath.Join(tmp, "zoxide") + + // Script that prints mock directories + script := "#!/bin/sh\necho \"/home/verse/project1\n/home/verse/docs\n/home/verse/dev/iris\"" + os.WriteFile(mockZoxide, []byte(script), 0755) + + // Add tmp to PATH + oldPath := os.Getenv("PATH") + os.Setenv("PATH", tmp+string(os.PathListSeparator)+oldPath) + defer os.Setenv("PATH", oldPath) + + gen := fs.ZoxideGenerator() + + t.Run("Query returns correct result when partial is empty", func(t *testing.T) { + results := gen([]string{"z", ""}, "z ", "") + if len(results) == 0 { + t.Errorf("Expected results from zoxide history, got 0") + } + }) + + t.Run("Path replaces home dir with ~", func(t *testing.T) { + home, _ := os.UserHomeDir() + results := gen([]string{"z", ""}, "z ", "") + foundHome := false + for _, r := range results { + if strings.HasPrefix(r.Desc, "~") { + foundHome = true + break + } + } + if !foundHome && home != "" { + t.Logf("Warning: Did not find ~ in descriptions, home is %s", home) + } + }) + + t.Run("Sort by descending score", func(t *testing.T) { + results := gen([]string{"z", "i"}, "z ", "i") + if len(results) >= 1 { + if results[0].Cmd == "" { + t.Errorf("Empty result command") + } + } + }) +} diff --git a/tests/integration_test.go b/tests/integration_test.go new file mode 100644 index 0000000..0095149 --- /dev/null +++ b/tests/integration_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/versenilvis/iris/commands/core" + _ "github.com/versenilvis/iris/commands/fs" // Register z command +) + +func TestIntegration_ZoxideMultiWord(t *testing.T) { + // Create mock directory with spaces + tmp := t.TempDir() + targetDir := filepath.Join(tmp, "My Awesome Project") + os.MkdirAll(targetDir, 0755) + + // Mock shell environment + oldWd, _ := os.Getwd() + os.Chdir(tmp) + defer os.Chdir(oldWd) + + // Mock zoxide binary + mockBinDir := t.TempDir() + mockZoxide := filepath.Join(mockBinDir, "zoxide") + script := "#!/bin/sh\necho \"" + targetDir + "\"" + os.WriteFile(mockZoxide, []byte(script), 0755) + + oldPath := os.Getenv("PATH") + os.Setenv("PATH", mockBinDir+string(os.PathListSeparator)+oldPath) + defer os.Setenv("PATH", oldPath) + + t.Run("z matches multi-word folder without quotes", func(t *testing.T) { + // Simulating user typing "z My Awe" + input := "z My Awe" + results := core.Lookup(input) + + found := false + for _, r := range results { + // Expected result should be "z My Awesome Project/" + if strings.Contains(r.Cmd, "My Awesome Project") { + found = true + // Check for word duplication bug: "z My My Awesome..." + if strings.Contains(r.Cmd, "My My") { + t.Errorf("Word duplication detected in suggestion: %s", r.Cmd) + } + } + } + if !found { + t.Errorf("Could not find 'My Awesome Project' in suggestions for %q", input) + } + }) +} diff --git a/tests/root/suggestions_test.go b/tests/root/suggestions_test.go new file mode 100644 index 0000000..422290d --- /dev/null +++ b/tests/root/suggestions_test.go @@ -0,0 +1,50 @@ +package root_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/versenilvis/iris/commands/core" + "github.com/versenilvis/iris/root" +) + +func TestMergeResults(t *testing.T) { + // Setup mock Registry + core.Registry = make(map[string]*core.Spec) + core.Register(&core.Spec{ + Name: "ls", + Description: "list files", + }) + + // Setup mock history file + tmp := t.TempDir() + histFile := filepath.Join(tmp, ".bash_history") + os.WriteFile(histFile, []byte("ls -l\ncd /tmp\ngit status\n"), 0644) + + oldHome := os.Getenv("HOME") + os.Setenv("HOME", tmp) + defer os.Setenv("HOME", oldHome) + + t.Run("History mode returns history items", func(t *testing.T) { + results := root.MergeResults("", "history") + if len(results) == 0 { + t.Errorf("MergeResults history mode returned 0 items") + } + }) + + t.Run("Spec mode returns command results and dedups", func(t *testing.T) { + core.Register(&core.Spec{Name: "git", Description: "git"}) + + results := root.MergeResults("gi", "spec") + foundGit := false + for _, r := range results { + if r.Cmd == "git" { + foundGit = true + } + } + if !foundGit { + t.Errorf("MergeResults spec mode did not find 'git'") + } + }) +} diff --git a/tests/shell/adapter_test.go b/tests/shell/adapter_test.go new file mode 100644 index 0000000..66630f1 --- /dev/null +++ b/tests/shell/adapter_test.go @@ -0,0 +1,62 @@ +package shell_test + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/versenilvis/iris/integration/shell" +) + +func TestSplitAliasTokens(t *testing.T) { + tests := []struct { + input string + expected []string + }{ + {"g='git commit'", []string{"g='git commit'"}}, + {"a=b c=d", []string{"a=b", "c=d"}}, + {"ta='tmux a -t' l='ls -l'", []string{"ta='tmux a -t'", "l='ls -l'"}}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + got := shell.SplitAliasTokens(tt.input) + if !reflect.DeepEqual(got, tt.expected) { + t.Errorf("SplitAliasTokens(%q) = %v; want %v", tt.input, got, tt.expected) + } + }) + } +} + +func TestScanPosixAliases(t *testing.T) { + tmp := t.TempDir() + + aliasFile := filepath.Join(tmp, ".bashrc") + content := ` +# some comments +alias g='git' +alias gca='git commit -a' +alias multi="a" b="c" +` + os.WriteFile(aliasFile, []byte(content), 0644) + + oldHome := os.Getenv("HOME") + os.Setenv("HOME", tmp) + defer os.Setenv("HOME", oldHome) + + aliases := shell.ScanPosixAliases([]string{".bashrc"}) + + expected := map[string]string{ + "g": "git", + "gca": "git commit -a", + "multi": "a", + "b": "c", + } + + for k, v := range expected { + if aliases[k] != v { + t.Errorf("Expected alias %s=%s, got %s", k, v, aliases[k]) + } + } +}