From 634c628928f6e039f3482663521678a147b021b3 Mon Sep 17 00:00:00 2001 From: Bor Kae Hwang Date: Tue, 1 May 2018 12:39:06 -0600 Subject: [PATCH] Add mrun option to run multiple targets at once iBazel currently only supports "run" for single target. Running multiple targets requires separate Bazel command executions and busy waiting for build processes. Implement "mrun" command which takes in arbitrary number of targets, and run all of them at once. We can pass in corresponding arguments with prefix "--arg=". --- bazel/bazel.go | 2 +- ibazel/command/command.go | 15 +- ibazel/command/default_command.go | 10 +- ibazel/command/notify_command.go | 6 +- ibazel/ibazel.go | 238 ++++++++++++++++++++++++++++-- ibazel/ibazel_test.go | 71 +++++++++ ibazel/main.go | 17 ++- ibazel/main_test.go | 25 +++- 8 files changed, 350 insertions(+), 34 deletions(-) diff --git a/bazel/bazel.go b/bazel/bazel.go index ab896b5c..82a5a42c 100644 --- a/bazel/bazel.go +++ b/bazel/bazel.go @@ -207,7 +207,7 @@ func (b *bazel) Test(args ...string) (*bytes.Buffer, error) { func (b *bazel) Run(args ...string) (*exec.Cmd, *bytes.Buffer, error) { b.WriteToStderr(true) b.WriteToStdout(true) - stdoutBuffer, stderrBuffer := b.newCommand("run", args...) + stdoutBuffer, stderrBuffer := b.newCommand("run", append(b.args, args...)...) b.cmd.Stdin = os.Stdin _, _ = stdoutBuffer.Write(stderrBuffer.Bytes()) diff --git a/ibazel/command/command.go b/ibazel/command/command.go index 38e24d6c..f4820c4a 100644 --- a/ibazel/command/command.go +++ b/ibazel/command/command.go @@ -31,15 +31,15 @@ var bazelNew = bazel.New // Command is an object that wraps the logic of running a task in Bazel and // manipulating it. type Command interface { - Start() (*bytes.Buffer, error) + Start(logFile *os.File) (*bytes.Buffer, error) Terminate() - NotifyOfChanges() *bytes.Buffer + NotifyOfChanges(logFile *os.File) *bytes.Buffer IsSubprocessRunning() bool } // start will be called by most implementations since this logic is extremely // common. -func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, *exec.Cmd) { +func start(b bazel.Bazel, target string, args []string, logFile *os.File) (*bytes.Buffer, *exec.Cmd) { tmpfile, err := ioutil.TempFile("", "bazel_script_path") if err != nil { fmt.Print(err) @@ -57,8 +57,13 @@ func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, *exec.Cm // Now that we have built the target, construct a executable form of it for // execution in a go routine. cmd := execCommand(runScriptPath, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr + if logFile != nil { + cmd.Stdout = logFile + cmd.Stderr = logFile + } else { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } // Set a process group id (PGID) on the subprocess. This is cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} diff --git a/ibazel/command/default_command.go b/ibazel/command/default_command.go index e8107fec..0b3e1e74 100644 --- a/ibazel/command/default_command.go +++ b/ibazel/command/default_command.go @@ -55,14 +55,14 @@ func (c *defaultCommand) Terminate() { c.cmd = nil } -func (c *defaultCommand) Start() (*bytes.Buffer, error) { +func (c *defaultCommand) Start(logFile *os.File) (*bytes.Buffer, error) { b := bazelNew() b.SetArguments(c.bazelArgs) b.WriteToStderr(true) b.WriteToStdout(true) - outputBuffer, foo := start(b, c.target, c.args) + outputBuffer, foo := start(b, c.target, c.args, logFile) c.cmd = foo c.cmd.Env = os.Environ() @@ -76,10 +76,10 @@ func (c *defaultCommand) Start() (*bytes.Buffer, error) { return outputBuffer, nil } -func (c *defaultCommand) NotifyOfChanges() *bytes.Buffer { +func (c *defaultCommand) NotifyOfChanges(logFile *os.File) *bytes.Buffer { c.Terminate() - c.Start() - return nil + outputBuffer, _ := c.Start(logFile) + return outputBuffer } func (c *defaultCommand) IsSubprocessRunning() bool { diff --git a/ibazel/command/notify_command.go b/ibazel/command/notify_command.go index 5f10346c..43ee9d3e 100644 --- a/ibazel/command/notify_command.go +++ b/ibazel/command/notify_command.go @@ -57,14 +57,14 @@ func (c *notifyCommand) Terminate() { c.cmd = nil } -func (c *notifyCommand) Start() (*bytes.Buffer, error) { +func (c *notifyCommand) Start(logFile *os.File) (*bytes.Buffer, error) { b := bazelNew() b.SetArguments(c.bazelArgs) b.WriteToStderr(true) b.WriteToStdout(true) - outputBuffer, foo := start(b, c.target, c.args) + outputBuffer, foo := start(b, c.target, c.args, logFile) c.cmd = foo // Keep the writer around. var err error @@ -84,7 +84,7 @@ func (c *notifyCommand) Start() (*bytes.Buffer, error) { return outputBuffer, nil } -func (c *notifyCommand) NotifyOfChanges() *bytes.Buffer { +func (c *notifyCommand) NotifyOfChanges(logFile *os.File) *bytes.Buffer { b := bazelNew() b.SetArguments(c.bazelArgs) diff --git a/ibazel/ibazel.go b/ibazel/ibazel.go index 9bb53c59..78756e61 100644 --- a/ibazel/ibazel.go +++ b/ibazel/ibazel.go @@ -17,10 +17,12 @@ package main import ( "bytes" "errors" + "flag" "fmt" "os" "os/signal" "path/filepath" + "regexp" "strings" "syscall" "time" @@ -40,9 +42,11 @@ var osExit = os.Exit var bazelNew = bazel.New var commandDefaultCommand = command.DefaultCommand var commandNotifyCommand = command.NotifyCommand +var mrunToFiles = flag.Bool("mrunToFiles", false, "Log mrun to file for simpler log reading") type State string type runnableCommand func(...string) (*bytes.Buffer, error) +type runnableCommands func([]string, [][]string, int) ([]*bytes.Buffer, error) const ( DEBOUNCE_QUERY State = "DEBOUNCE_QUERY" @@ -59,9 +63,15 @@ const buildQuery = "buildfiles(deps(set(%s)))" type IBazel struct { debounceDuration time.Duration - cmd command.Command - args []string - bazelArgs []string + cmd command.Command + cmds map[string]command.Command + logFiles map[string]*os.File + fileToProcesses map[string][]string + bldfToProcesses map[string][]string + prev string + firstBuildPassed bool + args []string + bazelArgs []string sigs chan os.Signal // Signals channel for the current process interruptCount int @@ -85,7 +95,7 @@ func New() (*IBazel, error) { if err != nil { return nil, err } - + i.firstBuildPassed = false i.debounceDuration = 100 * time.Millisecond i.filesWatched = map[*fsnotify.Watcher]map[string]bool{} i.workspaceFinder = &workspace_finder.MainWorkspaceFinder{} @@ -125,6 +135,11 @@ func (i *IBazel) handleSignals() { switch sig { case syscall.SIGINT: + for _, cmd := range i.cmds { + if cmd.IsSubprocessRunning() { + cmd.Terminate() + } + } if i.cmd != nil && i.cmd.IsSubprocessRunning() { fmt.Fprintf(os.Stderr, "\nSubprocess killed from getting SIGINT\n") i.cmd.Terminate() @@ -133,6 +148,11 @@ func (i *IBazel) handleSignals() { } break case syscall.SIGTERM: + for _, cmd := range i.cmds { + if cmd.IsSubprocessRunning() { + cmd.Terminate() + } + } if i.cmd != nil && i.cmd.IsSubprocessRunning() { fmt.Fprintf(os.Stderr, "\nSubprocess killed from getting SIGTERM\n") i.cmd.Terminate() @@ -140,6 +160,11 @@ func (i *IBazel) handleSignals() { osExit(3) return case syscall.SIGHUP: + for _, cmd := range i.cmds { + if cmd.IsSubprocessRunning() { + cmd.Terminate() + } + } if i.cmd != nil && i.cmd.IsSubprocessRunning() { fmt.Fprintf(os.Stderr, "\nSubprocess killed from getting SIGHUP\n") i.cmd.Terminate() @@ -241,6 +266,13 @@ func (i *IBazel) Run(target string, args []string) error { return i.loop("run", i.run, []string{target}) } +// Run the specified target (singular) in the IBazel loop. +func (i *IBazel) RunMulitple(args, target []string, debugArgs [][]string) error { + i.args = args + argsLength := len(args) + return i.loopMultiple("run", i.runMulitple, target, debugArgs, argsLength) +} + // Build the specified targets in the IBazel loop. func (i *IBazel) Build(targets ...string) error { return i.loop("build", i.build, targets) @@ -262,6 +294,15 @@ func (i *IBazel) loop(command string, commandToRun runnableCommand, targets []st return nil } +func (i *IBazel) loopMultiple(command string, commandToRun runnableCommands, targets []string, debugArgs [][]string, argsLength int) error { + i.state = QUERY + for { + i.iterationMultiple(command, commandToRun, targets, debugArgs, argsLength) + } + + return nil +} + // fsnotify also triggers for file stat and read operations. Explicitly filter the modifying events // to avoid triggering builds on file acccesses (e.g. due to your IDE checking modified status). const modifyingEvents = fsnotify.Write | fsnotify.Create | fsnotify.Rename | fsnotify.Remove @@ -318,6 +359,80 @@ func (i *IBazel) iteration(command string, commandToRun runnableCommand, targets } } +func (i *IBazel) iterationMultiple(command string, commandToRun runnableCommands, targets []string, debugArgs [][]string, argsLength int) { + fmt.Fprintf(os.Stderr, "State: %s\n", i.state) + switch i.state { + case WAIT: + select { + case e := <-i.sourceEventHandler.SourceFileEvents: + if e.Op&modifyingEvents != 0 { + fmt.Fprintf(os.Stderr, "Changed: %q. Rebuilding...\n", e.Name) + i.changeDetected(targets, "source", e.Name) + i.state = DEBOUNCE_RUN + } + case e := <-i.buildFileWatcher.Events: + if e.Op&modifyingEvents != 0 { + fmt.Fprintf(os.Stderr, "Build graph changed: %q. Requerying...\n", e.Name) + i.changeDetected(targets, "graph", e.Name) + i.state = DEBOUNCE_QUERY + } + } + case DEBOUNCE_QUERY: + select { + case e := <-i.buildFileWatcher.Events: + if e.Op&modifyingEvents != 0 { + i.changeDetected(targets, "graph", e.Name) + } + i.prev = e.Name + i.state = DEBOUNCE_QUERY + case <-time.After(i.debounceDuration): + i.state = QUERY + } + case QUERY: + // Query for which files to watch. + fmt.Fprintf(os.Stderr, "Querying for BUILD files...\n") + var toQuery []string + if i.prev != "" { + toQuery = i.bldfToProcesses[i.prev] + } + //new file added need to rebuild all and add to graphs + if len(toQuery) == 0 { + toQuery = targets + } + i.watchManyFiles(buildQuery, toQuery, i.buildFileWatcher, &i.bldfToProcesses) + fmt.Fprintf(os.Stderr, "Querying for source files...\n") + i.watchManyFiles(sourceQuery, toQuery, i.sourceFileWatcher, &i.fileToProcesses) + i.prev = "" + i.state = RUN + case DEBOUNCE_RUN: + select { + case e := <-i.sourceEventHandler.SourceFileEvents: + if e.Op&modifyingEvents != 0 { + i.changeDetected(targets, "source", e.Name) + } + i.prev = e.Name + i.state = DEBOUNCE_RUN + case <-time.After(i.debounceDuration): + i.state = RUN + } + case RUN: + var torun []string + if i.prev != "" && i.firstBuildPassed { + torun = i.fileToProcesses[i.prev] + } else { + torun = targets + } + fmt.Fprintf(os.Stderr, "%sing %s\n", strings.Title(command), strings.Join(torun, " ")) + i.beforeCommand(torun, command) + outputBuffers, err := commandToRun(torun, debugArgs, argsLength) + for _, buffer := range outputBuffers { + i.afterCommand(torun, command, err == nil, buffer) + } + i.prev = "" + i.state = WAIT + } +} + func (i *IBazel) build(targets ...string) (*bytes.Buffer, error) { b := i.newBazel() @@ -355,7 +470,27 @@ func contains(l []string, e string) bool { return false } -func (i *IBazel) setupRun(target string) command.Command { +func openFileForLogs(fileToOpen string) *os.File { + if !*mrunToFiles { + return nil + } + + reg, err1 := regexp.Compile("[^a-zA-Z0-9-]+") + if err1 != nil { + println(err1) + } + processedString := reg.ReplaceAllString(fileToOpen, "") + os.MkdirAll("/tmp/running/", os.ModePerm) + filename := fmt.Sprintf("/tmp/running/%s.txt", processedString) + file, err2 := os.OpenFile(filename, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) + if err2 != nil { + println(err2) + return nil + } + return file +} + +func (i *IBazel) setupRun(target string, debugArg []string, argsLength int) command.Command { rule, err := i.queryRule(target) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) @@ -376,6 +511,13 @@ func (i *IBazel) setupRun(target string) command.Command { fmt.Fprintf(os.Stderr, "Launching with notifications\n") return commandNotifyCommand(i.bazelArgs, target, i.args) } else { + // argsLength == -1 when the command is `run` + // no need to modify i.args + if len(debugArg) > 0 { + i.args = append(debugArg, i.args[len(i.args)-argsLength:len(i.args)]...) + } else if argsLength > -1 { + i.args = i.args[len(i.args)-argsLength:len(i.args)] + } return commandDefaultCommand(i.bazelArgs, target, i.args) } } @@ -384,8 +526,8 @@ func (i *IBazel) run(targets ...string) (*bytes.Buffer, error) { if i.cmd == nil { // If the command is empty, we are in our first pass through the state // machine and we need to make a command object. - i.cmd = i.setupRun(targets[0]) - outputBuffer, err := i.cmd.Start() + i.cmd = i.setupRun(targets[0], []string{}, -1) + outputBuffer, err := i.cmd.Start(nil) if err != nil { fmt.Fprintf(os.Stderr, "Run start failed %v\n", err) } @@ -393,17 +535,53 @@ func (i *IBazel) run(targets ...string) (*bytes.Buffer, error) { } fmt.Fprintf(os.Stderr, "Notifying of changes\n") - outputBuffer := i.cmd.NotifyOfChanges() + outputBuffer := i.cmd.NotifyOfChanges(nil) return outputBuffer, nil } +func (i *IBazel) runMulitple(targets []string, debugArgs [][]string, argsLength int) ([]*bytes.Buffer, error) { + + var outputBuffers []*bytes.Buffer + fmt.Fprintf(os.Stderr, "Rebuilding changed targets\n") + outputBufferBuild, errBuild := i.build(targets...) + i.afterCommand(targets, "build", errBuild == nil, outputBufferBuild) + if errBuild != nil { + return append(outputBuffers, outputBufferBuild), errBuild + } + i.firstBuildPassed = true + if i.cmds == nil { + i.cmds = make(map[string]command.Command) + i.logFiles = make(map[string]*os.File) + // If the commands are empty, we are in our first pass through the state + // machine and we need to make command objects. + for idx, targeter := range targets { + i.logFiles[targeter] = openFileForLogs(targeter) + newcommand := i.setupRun(targets[idx], debugArgs[idx], argsLength) + i.cmds[targeter] = newcommand + outputBuffer, err := newcommand.Start(i.logFiles[targeter]) + outputBuffers = append(outputBuffers, outputBuffer) + if err != nil { + fmt.Fprintf(os.Stderr, "Run start failed %v\n", err) + return outputBuffers, err + } + } + return outputBuffers, nil + } + fmt.Fprintf(os.Stderr, "Notifying of changes\n") + for _, targeter := range targets { + outputBuffers = append(outputBuffers, i.cmds[targeter].NotifyOfChanges(i.logFiles[targeter])) + } + return outputBuffers, nil +} + func (i *IBazel) queryRule(rule string) (*blaze_query.Rule, error) { b := i.newBazel() res, err := b.Query(rule) if err != nil { fmt.Fprintf(os.Stderr, "Error running Bazel %v\n", err) - osExit(4) + i.sigs <- syscall.SIGTERM + time.Sleep(10 * time.Second) } for _, target := range res.Target { @@ -434,13 +612,15 @@ func (i *IBazel) queryForSourceFiles(query string) []string { res, err := b.Query(query) if err != nil { fmt.Fprintf(os.Stderr, "Error running Bazel %v\n", err) - osExit(4) + i.sigs <- syscall.SIGTERM + time.Sleep(10 * time.Second) } workspacePath, err := i.workspaceFinder.FindWorkspace() if err != nil { fmt.Fprintf(os.Stderr, "Error finding workspace: %v\n", err) - osExit(5) + i.sigs <- syscall.SIGTERM + time.Sleep(10 * time.Second) } toWatch := make([]string, 0, 10000) @@ -469,7 +649,43 @@ func (i *IBazel) queryForSourceFiles(query string) []string { func (i *IBazel) watchFiles(query string, watcher *fsnotify.Watcher) { toWatch := i.queryForSourceFiles(query) filesAdded := map[string]bool{} + for _, line := range toWatch { + err := watcher.Add(line) + if err != nil { + fmt.Fprintf(os.Stderr, "Error watching file %v\nError: %v\n", line, err) + continue + } else { + filesAdded[line] = true + } + } + + for line, _ := range i.filesWatched[watcher] { + _, ok := filesAdded[line] + if !ok { + err := watcher.Remove(line) + if err != nil { + fmt.Fprintf(os.Stderr, "Error unwatching file %v\nError: %v\n", line, err) + } + } + } + fmt.Fprintf(os.Stderr, "Watching: %d files\n", len(filesAdded)) + i.filesWatched[watcher] = filesAdded +} + +func (i *IBazel) watchManyFiles(query string, targets []string, watcher *fsnotify.Watcher, filestorage *map[string][]string) { + var watchArray = make(map[string][]string) + for _, target := range targets { + watchArray[target] = i.queryForSourceFiles(fmt.Sprintf(query, target)) + } + *filestorage = make(map[string][]string) + for _, target := range targets { + for _, sourcefile := range watchArray[target] { + (*filestorage)[sourcefile] = append((*filestorage)[sourcefile], target) + } + } + toWatch := i.queryForSourceFiles(fmt.Sprintf(query, strings.Join(targets, " "))) + filesAdded := map[string]bool{} for _, line := range toWatch { err := watcher.Add(line) if err != nil { diff --git a/ibazel/ibazel_test.go b/ibazel/ibazel_test.go index 01112a59..f27ff8f6 100644 --- a/ibazel/ibazel_test.go +++ b/ibazel/ibazel_test.go @@ -213,6 +213,77 @@ func TestIBazelLoop(t *testing.T) { assertState(WAIT) } +func TestIBazelLoopMultiple(t *testing.T) { + i := newIBazel(t) + + // Replace the file watching channel with one that has a buffer. + i.buildFileWatcher.Events = make(chan fsnotify.Event, 1) + i.sourceEventHandler.SourceFileEvents = make(chan fsnotify.Event, 1) + + defer i.Cleanup() + + // The process for testing this is going to be to emit events to the channels + // that are associated with these objects and walk the state transition + // graph. + + // First let's consume all the events from all the channels we care about + called := false + command := func(targets []string, debugArgs [][]string, argsLength int) ([]*bytes.Buffer, error) { + called = true + return nil, nil + } + + i.state = QUERY + step := func() { + i.iterationMultiple("demo", command, []string{}, [][]string{}, 0) + } + assertRun := func() { + if called == false { + _, file, line, _ := runtime.Caller(1) // decorate + log + public function. + t.Errorf("%s:%v Should have run the provided comand", file, line) + } + called = false + } + assertState := func(state State) { + if i.state != state { + _, file, line, _ := runtime.Caller(1) // decorate + log + public function. + t.Errorf("%s:%v Expected state to be %s but was %s", file, line, state, i.state) + } + } + + // Pretend a fairly normal event chain happens. + // Start, run the program, write a source file, run, write a build file, run. + + assertState(QUERY) + step() + assertState(RUN) + step() // Actually run the command + assertRun() + assertState(WAIT) + // Source file change. + i.sourceEventHandler.SourceFileEvents <- fsnotify.Event{Op: fsnotify.Write} + step() + assertState(DEBOUNCE_RUN) + step() + // Don't send another event in to test the timer + assertState(RUN) + step() // Actually run the command + assertRun() + assertState(WAIT) + // Build file change. + i.buildFileWatcher.Events <- fsnotify.Event{Op: fsnotify.Write} + step() + assertState(DEBOUNCE_QUERY) + // Don't send another event in to test the timer + step() + assertState(QUERY) + step() + assertState(RUN) + step() // Actually run the command + assertRun() + assertState(WAIT) +} + func TestIBazelBuild(t *testing.T) { i := newIBazel(t) defer i.Cleanup() diff --git a/ibazel/main.go b/ibazel/main.go index 52b8f25b..91d4d800 100644 --- a/ibazel/main.go +++ b/ibazel/main.go @@ -65,7 +65,7 @@ func isOverrideableBazelFlag(arg string) bool { return false } -func parseArgs(in []string) (targets, bazelArgs, args []string) { +func parseArgs(in []string) (targets, bazelArgs, args []string, debugArgs [][]string) { afterDoubleDash := false for _, arg := range in { if afterDoubleDash { @@ -85,7 +85,16 @@ func parseArgs(in []string) (targets, bazelArgs, args []string) { } // If none of those things then it's probably a target. - targets = append(targets, arg) + if strings.HasPrefix(arg, "--arg") { + parsedArg := strings.Replace(arg, "--arg=", "", -1) + if strings.Contains(parsedArg, " ") { + parsedArg = "\"" + parsedArg + "\"" + } + debugArgs[len(debugArgs)-1] = append(debugArgs[len(debugArgs)-1], parsedArg) + } else { + targets = append(targets, arg) + debugArgs = append(debugArgs, []string{}) + } } } return @@ -132,7 +141,7 @@ func main() { } func handle(i *IBazel, command string, args []string) { - targets, bazelArgs, args := parseArgs(args) + targets, bazelArgs, args, debugArgs := parseArgs(args) i.SetBazelArgs(bazelArgs) switch command { @@ -143,6 +152,8 @@ func handle(i *IBazel, command string, args []string) { case "run": // Run only takes one argument i.Run(targets[0], args) + case "mrun": + i.RunMulitple(args, targets, debugArgs) default: fmt.Fprintf(os.Stderr, "Asked me to perform %s. I don't know how to do that.", command) usage() diff --git a/ibazel/main_test.go b/ibazel/main_test.go index a40f368e..f83058e4 100644 --- a/ibazel/main_test.go +++ b/ibazel/main_test.go @@ -25,19 +25,28 @@ func TestParsingArgs(t *testing.T) { targets []string bazelArgs []string args []string + debugArgs [][]string }{ // Empty case. - {[]string{}, nil, nil, nil}, + {[]string{}, nil, nil, nil, nil}, + // Only one target. + {[]string{"//my/target"}, []string{"//my/target"}, nil, nil, [][]string{{}}}, // Only targets. - {[]string{"//my/target"}, []string{"//my/target"}, nil, nil}, + {[]string{"//my/target1", "//my/target2"}, []string{"//my/target1", "//my/target2"}, nil, nil, [][]string{{},{}}}, // arguments after a --. - {[]string{"--", "--my_program_flag"}, nil, nil, []string{"--my_program_flag"}}, + {[]string{"--", "--my_program_flag"}, nil, nil, []string{"--my_program_flag"}, nil}, // Whitelisted bazel flag. - {[]string{"--test_output=streaming"}, nil, []string{"--test_output=streaming"}, nil}, + {[]string{"--test_output=streaming"}, nil, []string{"--test_output=streaming"}, nil, nil}, // Whitelisted bazel flag, arg, and target. - {[]string{"--test_output=streaming", "--", "--my_program_flag"}, nil, []string{"--test_output=streaming"}, []string{"--my_program_flag"}}, + {[]string{"--test_output=streaming", "--", "--my_program_flag"}, nil, []string{"--test_output=streaming"}, []string{"--my_program_flag"}, nil}, + // Multiple targets with multiple arguments. + {[]string{"//my/target1", "--arg=t1_arg1", "--arg=t1_arg2", "//my/target2"}, []string{"//my/target1", "//my/target2"}, nil, nil, [][]string{{"t1_arg1", "t1_arg2"},{}}}, + // Multiple targets with single argument. + {[]string{"//my/target1", "--arg=t1_arg1", "//my/target2", "--arg=t2_arg1", "//my/target3", "--arg=t3_arg1"}, []string{"//my/target1", "//my/target2", "//my/target3"}, nil, nil, [][]string{{"t1_arg1"}, {"t2_arg1"}, {"t3_arg1"}}}, + // Multiple targets with argument with whitespace. + {[]string{"//my/target1", "--arg=t1 arg1", "//my/target2", "--arg=t2 arg1", "//my/target3"}, []string{"//my/target1", "//my/target2", "//my/target3"}, nil, nil, [][]string{{"\"t1 arg1\""}, {"\"t2 arg1\""}, {}}}, } { - targets, bazelArgs, args := parseArgs(c.in) + targets, bazelArgs, args, debugArgs := parseArgs(c.in) if !reflect.DeepEqual(c.targets, targets) { t.Errorf("Targets not equal for args: %v\nGot: %v\nWant: %v", c.in, targets, c.targets) @@ -50,6 +59,10 @@ func TestParsingArgs(t *testing.T) { t.Errorf("Additional args not equal for args: %v\nGot: %v\nWant: %v", c.in, args, c.args) } + if !reflect.DeepEqual(c.debugArgs, debugArgs) { + t.Errorf("Debug args not equal for debugArgs: %v\nGot: %v\nWant: %v", + c.in, debugArgs, c.debugArgs) + } } }