1package flags 2 3import ( 4 "bytes" 5 "io" 6 "os" 7 "path" 8 "path/filepath" 9 "reflect" 10 "runtime" 11 "strings" 12 "testing" 13) 14 15type TestComplete struct { 16} 17 18func (t *TestComplete) Complete(match string) []Completion { 19 options := []string{ 20 "hello world", 21 "hello universe", 22 "hello multiverse", 23 } 24 25 ret := make([]Completion, 0, len(options)) 26 27 for _, o := range options { 28 if strings.HasPrefix(o, match) { 29 ret = append(ret, Completion{ 30 Item: o, 31 }) 32 } 33 } 34 35 return ret 36} 37 38var completionTestOptions struct { 39 Verbose bool `short:"v" long:"verbose" description:"Verbose messages"` 40 Debug bool `short:"d" long:"debug" description:"Enable debug"` 41 Info bool `short:"i" description:"Display info"` 42 Version bool `long:"version" description:"Show version"` 43 Required bool `long:"required" required:"true" description:"This is required"` 44 Hidden bool `long:"hidden" hidden:"true" description:"This is hidden"` 45 46 AddCommand struct { 47 Positional struct { 48 Filename Filename 49 } `positional-args:"yes"` 50 } `command:"add" description:"add an item"` 51 52 AddMultiCommand struct { 53 Positional struct { 54 Filename []Filename 55 } `positional-args:"yes"` 56 Extra []Filename `short:"f"` 57 } `command:"add-multi" description:"add multiple items"` 58 59 AddMultiCommandFlag struct { 60 Files []Filename `short:"f"` 61 } `command:"add-multi-flag" description:"add multiple items via flags"` 62 63 RemoveCommand struct { 64 Other bool `short:"o"` 65 File Filename `short:"f" long:"filename"` 66 } `command:"rm" description:"remove an item"` 67 68 RenameCommand struct { 69 Completed TestComplete `short:"c" long:"completed"` 70 } `command:"rename" description:"rename an item"` 71} 72 73type completionTest struct { 74 Args []string 75 Completed []string 76 ShowDescriptions bool 77} 78 79var completionTests []completionTest 80 81func init() { 82 _, sourcefile, _, _ := runtime.Caller(0) 83 completionTestSourcedir := filepath.Join(filepath.SplitList(path.Dir(sourcefile))...) 84 85 completionTestFilename := []string{filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion_test.go")} 86 87 completionTests = []completionTest{ 88 { 89 // Short names 90 []string{"-"}, 91 []string{"--debug", "--required", "--verbose", "--version", "-i"}, 92 false, 93 }, 94 95 { 96 // Short names full 97 []string{"-i"}, 98 []string{"-i"}, 99 false, 100 }, 101 102 { 103 // Short names concatenated 104 []string{"-dv"}, 105 []string{"-dv"}, 106 false, 107 }, 108 109 { 110 // Long names 111 []string{"--"}, 112 []string{"--debug", "--required", "--verbose", "--version"}, 113 false, 114 }, 115 116 { 117 // Long names with descriptions 118 []string{"--"}, 119 []string{ 120 "--debug # Enable debug", 121 "--required # This is required", 122 "--verbose # Verbose messages", 123 "--version # Show version", 124 }, 125 true, 126 }, 127 128 { 129 // Long names partial 130 []string{"--ver"}, 131 []string{"--verbose", "--version"}, 132 false, 133 }, 134 135 { 136 // Commands 137 []string{""}, 138 []string{"add", "add-multi", "add-multi-flag", "rename", "rm"}, 139 false, 140 }, 141 142 { 143 // Commands with descriptions 144 []string{""}, 145 []string{ 146 "add # add an item", 147 "add-multi # add multiple items", 148 "add-multi-flag # add multiple items via flags", 149 "rename # rename an item", 150 "rm # remove an item", 151 }, 152 true, 153 }, 154 155 { 156 // Commands partial 157 []string{"r"}, 158 []string{"rename", "rm"}, 159 false, 160 }, 161 162 { 163 // Positional filename 164 []string{"add", filepath.Join(completionTestSourcedir, "completion")}, 165 completionTestFilename, 166 false, 167 }, 168 169 { 170 // Multiple positional filename (1 arg) 171 []string{"add-multi", filepath.Join(completionTestSourcedir, "completion")}, 172 completionTestFilename, 173 false, 174 }, 175 { 176 // Multiple positional filename (2 args) 177 []string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")}, 178 completionTestFilename, 179 false, 180 }, 181 { 182 // Multiple positional filename (3 args) 183 []string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")}, 184 completionTestFilename, 185 false, 186 }, 187 188 { 189 // Flag filename 190 []string{"rm", "-f", path.Join(completionTestSourcedir, "completion")}, 191 completionTestFilename, 192 false, 193 }, 194 195 { 196 // Flag short concat last filename 197 []string{"rm", "-of", path.Join(completionTestSourcedir, "completion")}, 198 completionTestFilename, 199 false, 200 }, 201 202 { 203 // Flag concat filename 204 []string{"rm", "-f" + path.Join(completionTestSourcedir, "completion")}, 205 []string{"-f" + completionTestFilename[0], "-f" + completionTestFilename[1]}, 206 false, 207 }, 208 209 { 210 // Flag equal concat filename 211 []string{"rm", "-f=" + path.Join(completionTestSourcedir, "completion")}, 212 []string{"-f=" + completionTestFilename[0], "-f=" + completionTestFilename[1]}, 213 false, 214 }, 215 216 { 217 // Flag concat long filename 218 []string{"rm", "--filename=" + path.Join(completionTestSourcedir, "completion")}, 219 []string{"--filename=" + completionTestFilename[0], "--filename=" + completionTestFilename[1]}, 220 false, 221 }, 222 223 { 224 // Flag long filename 225 []string{"rm", "--filename", path.Join(completionTestSourcedir, "completion")}, 226 completionTestFilename, 227 false, 228 }, 229 230 { 231 // Custom completed 232 []string{"rename", "-c", "hello un"}, 233 []string{"hello universe"}, 234 false, 235 }, 236 { 237 // Multiple flag filename 238 []string{"add-multi-flag", "-f", filepath.Join(completionTestSourcedir, "completion")}, 239 completionTestFilename, 240 false, 241 }, 242 } 243} 244 245func TestCompletion(t *testing.T) { 246 p := NewParser(&completionTestOptions, Default) 247 c := &completion{parser: p} 248 249 for _, test := range completionTests { 250 if test.ShowDescriptions { 251 continue 252 } 253 254 ret := c.complete(test.Args) 255 items := make([]string, len(ret)) 256 257 for i, v := range ret { 258 items[i] = v.Item 259 } 260 261 if !reflect.DeepEqual(items, test.Completed) { 262 t.Errorf("Args: %#v, %#v\n Expected: %#v\n Got: %#v", test.Args, test.ShowDescriptions, test.Completed, items) 263 } 264 } 265} 266 267func TestParserCompletion(t *testing.T) { 268 for _, test := range completionTests { 269 if test.ShowDescriptions { 270 os.Setenv("GO_FLAGS_COMPLETION", "verbose") 271 } else { 272 os.Setenv("GO_FLAGS_COMPLETION", "1") 273 } 274 275 tmp := os.Stdout 276 277 r, w, _ := os.Pipe() 278 os.Stdout = w 279 280 out := make(chan string) 281 282 go func() { 283 var buf bytes.Buffer 284 285 io.Copy(&buf, r) 286 287 out <- buf.String() 288 }() 289 290 p := NewParser(&completionTestOptions, None) 291 292 p.CompletionHandler = func(items []Completion) { 293 comp := &completion{parser: p} 294 comp.print(items, test.ShowDescriptions) 295 } 296 297 _, err := p.ParseArgs(test.Args) 298 299 w.Close() 300 301 os.Stdout = tmp 302 303 if err != nil { 304 t.Fatalf("Unexpected error: %s", err) 305 } 306 307 got := strings.Split(strings.Trim(<-out, "\n"), "\n") 308 309 if !reflect.DeepEqual(got, test.Completed) { 310 t.Errorf("Expected: %#v\nGot: %#v", test.Completed, got) 311 } 312 } 313 314 os.Setenv("GO_FLAGS_COMPLETION", "") 315} 316