package main import ( "fmt" "reflect" "strings" "testing" ) var gEvalTests = []struct { inp string toks []string exprs []expr }{ { "", []string{}, nil, }, { "# comments start with '#'", []string{}, nil, }, { "echo hello", []string{"echo", "hello", "\n"}, []expr{&callExpr{"echo", []string{"hello"}, 1}}, }, { "echo hello world", []string{"echo", "hello", "world", "\n"}, []expr{&callExpr{"echo", []string{"hello", "world"}, 1}}, }, { "echo 'hello world'", []string{"echo", "hello world", "\n"}, []expr{&callExpr{"echo", []string{"hello world"}, 1}}, }, { `echo "hello world"`, []string{"echo", "hello world", "\n"}, []expr{&callExpr{"echo", []string{"hello world"}, 1}}, }, { `echo "hello\"world"`, []string{"echo", `hello"world`, "\n"}, []expr{&callExpr{"echo", []string{`hello"world`}, 1}}, }, { `echo "hello\tworld"`, []string{"echo", "hello\tworld", "\n"}, []expr{&callExpr{"echo", []string{"hello\tworld"}, 1}}, }, { `echo "hello\nworld"`, []string{"echo", "hello\nworld", "\n"}, []expr{&callExpr{"echo", []string{"hello\nworld"}, 1}}, }, { `echo "hello\zworld"`, []string{"echo", `hello\zworld`, "\n"}, []expr{&callExpr{"echo", []string{`hello\zworld`}, 1}}, }, { `echo "hello\0world"`, []string{"echo", "hello\000world", "\n"}, []expr{&callExpr{"echo", []string{"hello\000world"}, 1}}, }, { `echo "hello\101world"`, []string{"echo", "hello\101world", "\n"}, []expr{&callExpr{"echo", []string{"hello\101world"}, 1}}, }, { `echo hello\ world`, []string{"echo", "hello world", "\n"}, []expr{&callExpr{"echo", []string{"hello world"}, 1}}, }, { "echo hello\\\tworld", []string{"echo", "hello\tworld", "\n"}, []expr{&callExpr{"echo", []string{"hello\tworld"}, 1}}, }, { "echo hello\\\nworld", []string{"echo", "hello\nworld", "\n"}, []expr{&callExpr{"echo", []string{"hello\nworld"}, 1}}, }, { `echo hello\\world`, []string{"echo", `hello\world`, "\n"}, []expr{&callExpr{"echo", []string{`hello\world`}, 1}}, }, { `echo hello\zworld`, []string{"echo", "hellozworld", "\n"}, []expr{&callExpr{"echo", []string{"hellozworld"}, 1}}, }, { "set hidden # trailing comments are allowed", []string{"set", "hidden", "\n"}, []expr{&setExpr{"hidden", ""}}, }, { "set hidden; set preview", []string{"set", "hidden", ";", "set", "preview", "\n"}, []expr{&setExpr{"hidden", ""}, &setExpr{"preview", ""}}, }, { "set hidden\nset preview", []string{"set", "hidden", "\n", "set", "preview", "\n"}, []expr{&setExpr{"hidden", ""}, &setExpr{"preview", ""}}, }, { `set ifs ""`, []string{"set", "ifs", "", "\n"}, []expr{&setExpr{"ifs", ""}}, }, { `set ifs "\n"`, []string{"set", "ifs", "\n", "\n"}, []expr{&setExpr{"ifs", "\n"}}, }, { "set ratios 1:2:3", []string{"set", "ratios", "1:2:3", "\n"}, []expr{&setExpr{"ratios", "1:2:3"}}, }, { "set ratios 1:2:3;", []string{"set", "ratios", "1:2:3", ";"}, []expr{&setExpr{"ratios", "1:2:3"}}, }, { ":set ratios 1:2:3", []string{":", "set", "ratios", "1:2:3", "\n", "\n"}, []expr{&listExpr{[]expr{&setExpr{"ratios", "1:2:3"}}, 1}}, }, { ":set ratios 1:2:3\nset hidden", []string{":", "set", "ratios", "1:2:3", "\n", "\n", "set", "hidden", "\n"}, []expr{&listExpr{[]expr{&setExpr{"ratios", "1:2:3"}}, 1}, &setExpr{"hidden", ""}}, }, { ":set ratios 1:2:3;", []string{":", "set", "ratios", "1:2:3", ";", "\n"}, []expr{&listExpr{[]expr{&setExpr{"ratios", "1:2:3"}}, 1}}, }, { ":set ratios 1:2:3;\nset hidden", []string{":", "set", "ratios", "1:2:3", ";", "\n", "set", "hidden", "\n"}, []expr{&listExpr{[]expr{&setExpr{"ratios", "1:2:3"}}, 1}, &setExpr{"hidden", ""}}, }, { "set ratios 1:2:3\n set hidden", []string{"set", "ratios", "1:2:3", "\n", "set", "hidden", "\n"}, []expr{&setExpr{"ratios", "1:2:3"}, &setExpr{"hidden", ""}}, }, { "set ratios 1:2:3 \nset hidden", []string{"set", "ratios", "1:2:3", "\n", "set", "hidden", "\n"}, []expr{&setExpr{"ratios", "1:2:3"}, &setExpr{"hidden", ""}}, }, { "setlocal /foo/bar hidden # trailing comments are allowed", []string{"setlocal", "/foo/bar", "hidden", "\n"}, []expr{&setLocalExpr{"/foo/bar", "hidden", ""}}, }, { "setlocal /foo/bar hidden; setlocal /foo/bar reverse", []string{"setlocal", "/foo/bar", "hidden", ";", "setlocal", "/foo/bar", "reverse", "\n"}, []expr{&setLocalExpr{"/foo/bar", "hidden", ""}, &setLocalExpr{"/foo/bar", "reverse", ""}}, }, { "setlocal /foo/bar hidden\nsetlocal /foo/bar reverse", []string{"setlocal", "/foo/bar", "hidden", "\n", "setlocal", "/foo/bar", "reverse", "\n"}, []expr{&setLocalExpr{"/foo/bar", "hidden", ""}, &setLocalExpr{"/foo/bar", "reverse", ""}}, }, { `setlocal /foo/bar info ""`, []string{"setlocal", "/foo/bar", "info", "", "\n"}, []expr{&setLocalExpr{"/foo/bar", "info", ""}}, }, { `setlocal /foo/bar info "size"`, []string{"setlocal", "/foo/bar", "info", "size", "\n"}, []expr{&setLocalExpr{"/foo/bar", "info", "size"}}, }, { "setlocal /foo/bar info size:time", []string{"setlocal", "/foo/bar", "info", "size:time", "\n"}, []expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, }, { "setlocal /foo/bar info size:time;", []string{"setlocal", "/foo/bar", "info", "size:time", ";"}, []expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, }, { ":setlocal /foo/bar info size:time", []string{":", "setlocal", "/foo/bar", "info", "size:time", "\n", "\n"}, []expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}}, }, { ":setlocal /foo/bar info size:time\nsetlocal /foo/bar hidden", []string{":", "setlocal", "/foo/bar", "info", "size:time", "\n", "\n", "setlocal", "/foo/bar", "hidden", "\n"}, []expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}, &setLocalExpr{"/foo/bar", "hidden", ""}}, }, { ":setlocal /foo/bar info size:time;", []string{":", "setlocal", "/foo/bar", "info", "size:time", ";", "\n"}, []expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}}, }, { ":setlocal /foo/bar info size:time;\nsetlocal /foo/bar hidden", []string{":", "setlocal", "/foo/bar", "info", "size:time", ";", "\n", "setlocal", "/foo/bar", "hidden", "\n"}, []expr{&listExpr{[]expr{&setLocalExpr{"/foo/bar", "info", "size:time"}}, 1}, &setLocalExpr{"/foo/bar", "hidden", ""}}, }, { "setlocal /foo/bar info size:time\n setlocal /foo/bar hidden", []string{"setlocal", "/foo/bar", "info", "size:time", "\n", "setlocal", "/foo/bar", "hidden", "\n"}, []expr{&setLocalExpr{"/foo/bar", "info", "size:time"}, &setLocalExpr{"/foo/bar", "hidden", ""}}, }, { "setlocal /foo/bar info size:time \nsetlocal /foo/bar hidden", []string{"setlocal", "/foo/bar", "info", "size:time", "\n", "setlocal", "/foo/bar", "hidden", "\n"}, []expr{&setLocalExpr{"/foo/bar", "info", "size:time"}, &setLocalExpr{"/foo/bar", "hidden", ""}}, }, { "map gh cd ~", []string{"map", "gh", "cd", "~", "\n"}, []expr{&mapExpr{"gh", &callExpr{"cd", []string{"~"}, 1}}}, }, { "map gh cd ~;", []string{"map", "gh", "cd", "~", ";"}, []expr{&mapExpr{"gh", &callExpr{"cd", []string{"~"}, 1}}}, }, { "map gh :cd ~", []string{"map", "gh", ":", "cd", "~", "\n", "\n"}, []expr{&mapExpr{"gh", &listExpr{[]expr{&callExpr{"cd", []string{"~"}, 1}}, 1}}}, }, { "map gh :cd ~;", []string{"map", "gh", ":", "cd", "~", ";", "\n"}, []expr{&mapExpr{"gh", &listExpr{[]expr{&callExpr{"cd", []string{"~"}, 1}}, 1}}}, }, { "cmap cmd-escape", []string{"cmap", "", "cmd-escape", "\n"}, []expr{&cmapExpr{"", &callExpr{"cmd-escape", nil, 1}}}, }, { "cmd usage $du -h . | less", []string{"cmd", "usage", "$", "du -h . | less", "\n"}, []expr{&cmdExpr{"usage", &execExpr{"$", "du -h . | less"}}}, }, { "cmd 世界 $echo 世界", []string{"cmd", "世界", "$", "echo 世界", "\n"}, []expr{&cmdExpr{"世界", &execExpr{"$", "echo 世界"}}}, }, { "map u usage", []string{"map", "u", "usage", "\n"}, []expr{&mapExpr{"u", &callExpr{"usage", nil, 1}}}, }, { "map u usage;", []string{"map", "u", "usage", ";"}, []expr{&mapExpr{"u", &callExpr{"usage", nil, 1}}}, }, { "map u :usage", []string{"map", "u", ":", "usage", "\n", "\n"}, []expr{&mapExpr{"u", &listExpr{[]expr{&callExpr{"usage", nil, 1}}, 1}}}, }, { "map u :usage;", []string{"map", "u", ":", "usage", ";", "\n"}, []expr{&mapExpr{"u", &listExpr{[]expr{&callExpr{"usage", nil, 1}}, 1}}}, }, { "map r push :rename", []string{"map", "r", "push", ":rename", "\n"}, []expr{&mapExpr{"r", &callExpr{"push", []string{":rename"}, 1}}}, }, { "map r push :rename;", []string{"map", "r", "push", ":rename;", "\n"}, []expr{&mapExpr{"r", &callExpr{"push", []string{":rename;"}, 1}}}, }, { "map r push :rename # trailing comments are allowed after a space", []string{"map", "r", "push", ":rename", "\n"}, []expr{&mapExpr{"r", &callExpr{"push", []string{":rename"}, 1}}}, }, { "map r :push :rename", []string{"map", "r", ":", "push", ":rename", "\n", "\n"}, []expr{&mapExpr{"r", &listExpr{[]expr{&callExpr{"push", []string{":rename"}, 1}}, 1}}}, }, { "map r :push :rename ; set hidden", []string{"map", "r", ":", "push", ":rename", ";", "set", "hidden", "\n", "\n"}, []expr{&mapExpr{"r", &listExpr{[]expr{&callExpr{"push", []string{":rename"}, 1}, &setExpr{"hidden", ""}}, 1}}}, }, { "map u $du -h . | less", []string{"map", "u", "$", "du -h . | less", "\n"}, []expr{&mapExpr{"u", &execExpr{"$", "du -h . | less"}}}, }, { "cmd usage $du -h $1 | less", []string{"cmd", "usage", "$", "du -h $1 | less", "\n"}, []expr{&cmdExpr{"usage", &execExpr{"$", "du -h $1 | less"}}}, }, { "map u usage /", []string{"map", "u", "usage", "/", "\n"}, []expr{&mapExpr{"u", &callExpr{"usage", []string{"/"}, 1}}}, }, { "map ss :set sortby size; set info size", []string{"map", "ss", ":", "set", "sortby", "size", ";", "set", "info", "size", "\n", "\n"}, []expr{&mapExpr{"ss", &listExpr{[]expr{&setExpr{"sortby", "size"}, &setExpr{"info", "size"}}, 1}}}, }, { "map ss :set sortby size; set info size;", []string{"map", "ss", ":", "set", "sortby", "size", ";", "set", "info", "size", ";", "\n"}, []expr{&mapExpr{"ss", &listExpr{[]expr{&setExpr{"sortby", "size"}, &setExpr{"info", "size"}}, 1}}}, }, { `cmd gohome :{{ cd ~ set hidden }}`, []string{ "cmd", "gohome", ":", "{{", "cd", "~", "\n", "set", "hidden", "\n", "}}", "\n", }, []expr{&cmdExpr{ "gohome", &listExpr{[]expr{ &callExpr{"cd", []string{"~"}, 1}, &setExpr{"hidden", ""}, }, 1}, }}, }, { `map gh :{{ cd ~ set hidden }}`, []string{ "map", "gh", ":", "{{", "cd", "~", "\n", "set", "hidden", "\n", "}}", "\n", }, []expr{&mapExpr{ "gh", &listExpr{[]expr{ &callExpr{"cd", []string{"~"}, 1}, &setExpr{"hidden", ""}, }, 1}, }}, }, { `map c ${{ mkdir foo cp $fs foo tar -czvf foo.tar.gz foo rm -rf foo }}`, []string{"map", "c", "$", "{{", ` mkdir foo cp $fs foo tar -czvf foo.tar.gz foo rm -rf foo `, "}}", "\n"}, []expr{&mapExpr{"c", &execExpr{"$", ` mkdir foo cp $fs foo tar -czvf foo.tar.gz foo rm -rf foo `}}}, }, { `cmd compress ${{ mkdir $1 cp $fs $1 tar -czvf $1.tar.gz $1 rm -rf $1 }}`, []string{"cmd", "compress", "$", "{{", ` mkdir $1 cp $fs $1 tar -czvf $1.tar.gz $1 rm -rf $1 `, "}}", "\n"}, []expr{&cmdExpr{"compress", &execExpr{"$", ` mkdir $1 cp $fs $1 tar -czvf $1.tar.gz $1 rm -rf $1 `}}}, }, } func TestScan(t *testing.T) { for _, test := range gEvalTests { s := newScanner(strings.NewReader(test.inp)) for _, tok := range test.toks { if s.scan(); s.tok != tok { t.Errorf("at input '%s' expected '%s' but scanned '%s'", test.inp, tok, s.tok) } } if s.scan() { t.Errorf("at input '%s' unexpected '%s'", test.inp, s.tok) } } } func TestParse(t *testing.T) { for _, test := range gEvalTests { p := newParser(strings.NewReader(test.inp)) for _, expr := range test.exprs { if p.parse(); !reflect.DeepEqual(p.expr, expr) { t.Errorf("at input '%s' expected '%s' but parsed '%s'", test.inp, expr, p.expr) } } if p.parse(); p.expr != nil { t.Errorf("at input '%s' unexpected '%s'", test.inp, p.expr) } } } func TestSplitKeys(t *testing.T) { inps := []struct { s string keys []string }{ {"", nil}, {"j", []string{"j"}}, {"jk", []string{"j", "k"}}, {"1j", []string{"1", "j"}}, {"42j", []string{"4", "2", "j"}}, {"", []string{""}}, {"j", []string{"j", ""}}, {"jk", []string{"j", "", "k"}}, {"1jk", []string{"1", "j", "", "k"}}, {"1j1k", []string{"1", "j", "", "1", "k"}}, {"<>", []string{"<>"}}, {"", []string{""}}, {">", []string{">", ""}}, {">>", []string{">", "", ">"}}, } for _, inp := range inps { if keys := splitKeys(inp.s); !reflect.DeepEqual(keys, inp.keys) { t.Errorf("at input '%s' expected '%v' but got '%v'", inp.s, inp.keys, keys) } } } func TestApplyBoolOpt(t *testing.T) { tests := []struct { opt bool e setExpr exp bool }{ {true, setExpr{"feature", ""}, true}, {true, setExpr{"feature", "true"}, true}, {true, setExpr{"feature", "false"}, false}, {false, setExpr{"feature", ""}, true}, {false, setExpr{"feature", "true"}, true}, {false, setExpr{"feature", "false"}, false}, {true, setExpr{"nofeature", ""}, false}, {false, setExpr{"nofeature", ""}, false}, {true, setExpr{"feature!", ""}, false}, {false, setExpr{"feature!", ""}, true}, } for _, test := range tests { testStr := fmt.Sprintf("%v", test) if err := applyBoolOpt(&test.opt, &test.e); err != nil { t.Errorf("at test '%s' expected '%t' but got an error '%s'", testStr, test.exp, err) continue } if test.opt != test.exp { t.Errorf("at test '%s' expected '%t' but got '%t'", testStr, test.exp, test.opt) } } } func TestApplyLocalBoolOpt(t *testing.T) { tests := []struct { localOpt map[string]bool globalOpt bool e setLocalExpr exp bool }{ {map[string]bool{}, false, setLocalExpr{"/", "feature", ""}, true}, {map[string]bool{}, false, setLocalExpr{"/", "feature", "true"}, true}, {map[string]bool{}, false, setLocalExpr{"/", "feature", "false"}, false}, {map[string]bool{}, false, setLocalExpr{"/", "nofeature", ""}, false}, {map[string]bool{}, true, setLocalExpr{"/", "feature!", ""}, false}, {map[string]bool{}, false, setLocalExpr{"/", "feature!", ""}, true}, {map[string]bool{"/": true}, false, setLocalExpr{"/", "feature", ""}, true}, {map[string]bool{"/": true}, false, setLocalExpr{"/", "feature", "true"}, true}, {map[string]bool{"/": true}, false, setLocalExpr{"/", "feature", "false"}, false}, {map[string]bool{"/": true}, false, setLocalExpr{"/", "nofeature", ""}, false}, {map[string]bool{"/": true}, true, setLocalExpr{"/", "feature!", ""}, false}, {map[string]bool{"/": true}, false, setLocalExpr{"/", "feature!", ""}, false}, {map[string]bool{"/": false}, false, setLocalExpr{"/", "feature", ""}, true}, {map[string]bool{"/": false}, false, setLocalExpr{"/", "feature", "true"}, true}, {map[string]bool{"/": false}, false, setLocalExpr{"/", "feature", "false"}, false}, {map[string]bool{"/": false}, false, setLocalExpr{"/", "nofeature", ""}, false}, {map[string]bool{"/": false}, true, setLocalExpr{"/", "feature!", ""}, true}, {map[string]bool{"/": false}, false, setLocalExpr{"/", "feature!", ""}, true}, } for _, test := range tests { testStr := fmt.Sprintf("%v", test) if err := applyLocalBoolOpt(test.localOpt, test.globalOpt, &test.e); err != nil { t.Errorf("at test '%s' expected '%t' but got an error '%s'", testStr, test.exp, err) continue } result := test.localOpt[test.e.path] if result != test.exp { t.Errorf("at test '%s' expected '%t' but got '%t'", testStr, test.exp, result) } } }