github.com/windmilleng/wat@v0.0.2-0.20180626175338-9349b638e250/cli/wat/wat.go (about) 1 package wat 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "os/exec" 9 "time" 10 11 "strconv" 12 13 "github.com/spf13/cobra" 14 ) 15 16 var CmdTimeout time.Duration 17 18 const Divider = "--------------------\n" 19 20 const appNameWat = "wat" 21 22 var dryRun bool 23 var numCmds int 24 25 var rootCmd = &cobra.Command{ 26 Use: "wat", 27 Short: "WAT (Win At Tests!) figures out what tests you should run next, and runs them for you", 28 Run: wat, 29 } 30 31 func init() { 32 rootCmd.PersistentFlags().DurationVarP(&CmdTimeout, "timeout", "t", 2*time.Minute, "timeout for training/running commands") 33 rootCmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "just print recommended commands, don't run them") 34 rootCmd.Flags().IntVarP(&numCmds, "numCmds", "n", nDecideCommands, "number of commands WAT should suggest/run") 35 36 rootCmd.AddCommand(initCmd) 37 rootCmd.AddCommand(recentCmd) 38 rootCmd.AddCommand(populateCmd) 39 rootCmd.AddCommand(listCmd) 40 rootCmd.AddCommand(trainCmd) 41 } 42 43 func Execute() (outerErr error) { 44 _, analyticsCmd, err := initAnalytics() 45 if err != nil { 46 return err 47 } 48 49 rootCmd.AddCommand(analyticsCmd) 50 51 return rootCmd.Execute() 52 } 53 54 func wat(_ *cobra.Command, args []string) { 55 ctx := context.Background() 56 57 ws, err := GetOrInitWatWorkspace() 58 if err != nil { 59 ws.Fatal("GetWatWorkspace", err) 60 } 61 62 // TODO: should probs be able to pass edits into `Decide` (or use the edits that 63 // `Decide` found) rather than needing to get them twice. 64 recentEdits, err := RecentFileNames(ws) 65 66 cmds, err := Decide(ctx, ws, numCmds) 67 if err != nil { 68 ws.Fatal("Decide", err) 69 } 70 71 if dryRun { 72 fmt.Fprintln(os.Stderr, "WAT recommends the following commands:") 73 } else { 74 fmt.Fprintln(os.Stderr, "WAT will run the following commands:") 75 } 76 for _, cmd := range cmds { 77 // print recommended cmds to terminal (properly escaped, but not wrapped in quotes, 78 // in case user wants to copy/paste, pipe somewhere, etc.) 79 safe := fmt.Sprintf("%q", cmd.Command) 80 fmt.Printf("\t%s\n", tryUnquote(safe)) 81 } 82 83 if dryRun { 84 // it's a dry run, don't actually run the commands 85 return 86 } 87 88 logContext := LogContext{ 89 RecentEdits: recentEdits, 90 StartTime: time.Now(), 91 Source: LogSourceUser, 92 } 93 94 err = RunCommands(ctx, ws, cmds, CmdTimeout, os.Stdout, os.Stderr, logContext) 95 if err != nil { 96 ws.Fatal("RunCommands", err) 97 } 98 } 99 100 func runCmdAndLog(ctx context.Context, root string, c WatCommand, outStream, errStream io.Writer) (CommandLog, error) { 101 start := time.Now() 102 103 err := runCmd(ctx, root, c.Command, outStream, errStream) 104 105 if ctx.Err() != nil { 106 // Propagate cancel/timeout errors 107 return CommandLog{}, ctx.Err() 108 } 109 110 if err != nil { 111 if _, ok := err.(*exec.ExitError); !ok { 112 // NOT an exit error, i.e. it's an unexpected error; stop execution. 113 return CommandLog{}, err 114 } 115 } 116 117 // Either we have no error, or an ExitError (i.e. expected case: cmd 118 // exited with non-zero exit code). 119 return CommandLog{ 120 Command: c.Command, 121 Success: err == nil, 122 Duration: time.Since(start), 123 }, nil 124 } 125 126 func runCmd(ctx context.Context, root, command string, outStream, errStream io.Writer) error { 127 cmd := exec.CommandContext(ctx, "bash", "-c", command) 128 cmd.Dir = root 129 cmd.Stdout = outStream 130 cmd.Stderr = errStream 131 132 return cmd.Run() 133 } 134 135 func runCmds(ctx context.Context, root string, cmds []WatCommand, timeout time.Duration, 136 outStream, errStream io.Writer) ([]CommandLog, error) { 137 logs := []CommandLog{} 138 139 timeoutCtx, cancel := context.WithTimeout(ctx, timeout) 140 defer cancel() 141 142 errStream.Write([]byte(Divider)) 143 for _, c := range cmds { 144 outStream.Write([]byte(c.PrettyCmd())) 145 146 log, err := runCmdAndLog(timeoutCtx, root, c, outStream, errStream) 147 if err != nil { 148 return logs, err 149 } 150 151 errStream.Write([]byte(Divider)) 152 logs = append(logs, log) 153 } 154 155 return logs, nil 156 } 157 158 // Runs the given commands and logs their results to file for use in making our ML model 159 func RunCommands(ctx context.Context, ws WatWorkspace, cmds []WatCommand, timeout time.Duration, 160 outStream, errStream io.Writer, logContext LogContext) error { 161 t := time.Now() 162 logs, err := runCmds(ctx, ws.Root(), cmds, timeout, outStream, errStream) 163 if err != nil { 164 // If we got an unexpected err running commands, don't bother logging 165 return err 166 } 167 ws.a.Timer(timerCommandsRun, time.Since(t), nil) 168 logGroup := CommandLogGroup{ 169 Logs: logs, 170 Context: logContext, 171 } 172 return CmdLogGroupsToFile(ws, []CommandLogGroup{logGroup}) 173 } 174 175 func tryUnquote(s string) string { 176 res, err := strconv.Unquote(s) 177 if err == nil { 178 return res 179 } 180 return s 181 }