Skip to content

Commit 3ad6036

Browse files
committed
Show when bazel run exited with code
1 parent 91e44ae commit 3ad6036

11 files changed

Lines changed: 147 additions & 104 deletions

File tree

internal/bazel/bazel.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ type Bazel interface {
142142
Norun(args ...string) (*bytes.Buffer, error)
143143
Test(args ...string) (*bytes.Buffer, error)
144144
Run(args ...string) (*exec.Cmd, *bytes.Buffer, error)
145-
Wait() error
146145
Cancel()
147146
}
148147

@@ -417,16 +416,6 @@ func (b *bazel) Run(args ...string) (*exec.Cmd, *bytes.Buffer, error) {
417416
return b.cmd, stderrBuffer, err
418417
}
419418

420-
func (b *bazel) Wait() error {
421-
res := b.cmd.Wait()
422-
if res.Error() == "exec: Wait was already called" {
423-
if b.cmd.ProcessState.Success() {
424-
return nil
425-
}
426-
}
427-
return res
428-
}
429-
430419
// Cancel the currently running operation. Useful if you call Run(target) and
431420
// would like to stop the action running in a goroutine.
432421
func (b *bazel) Cancel() {

internal/e2e/exit_code/BUILD

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
load("@io_bazel_rules_go//go/tools/bazel_testing:def.bzl", "go_bazel_test")
2+
3+
go_bazel_test(
4+
name = "exit_code_test",
5+
srcs = ["exit_code_test.go"],
6+
deps = ["//internal/e2e"],
7+
)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package lifecycle_hooks
2+
3+
import (
4+
"testing"
5+
6+
"github.com/bazelbuild/bazel-watcher/internal/e2e"
7+
)
8+
9+
const mainFiles = `
10+
-- BUILD.bazel --
11+
sh_binary(
12+
name = "test",
13+
srcs = ["test.sh"],
14+
)
15+
sh_binary(
16+
name = "failure",
17+
srcs = ["failure.sh"],
18+
)
19+
-- test.sh --
20+
-- failure.sh --
21+
exit 42
22+
`
23+
24+
func TestMain(m *testing.M) {
25+
e2e.TestMain(m, e2e.Args{
26+
Main: mainFiles,
27+
})
28+
}
29+
30+
func TestExitCode(t *testing.T) {
31+
ibazel := e2e.SetUp(t)
32+
defer ibazel.Kill()
33+
34+
ibazel.Run([]string{}, "//:test")
35+
ibazel.ExpectIBazelError("Exited \\(0\\)")
36+
}
37+
38+
func TestExitCodeFailure(t *testing.T) {
39+
ibazel := e2e.SetUp(t)
40+
defer ibazel.Kill()
41+
42+
ibazel.Run([]string{}, "//:failure")
43+
ibazel.ExpectIBazelError("Exited \\(42\\)")
44+
}

internal/ibazel/command/command.go

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"fmt"
2121
"io/ioutil"
2222
"os"
23-
"os/exec"
2423
"runtime"
2524
"strings"
2625
"syscall"
@@ -82,42 +81,22 @@ func start(b bazel.Bazel, target string, args []string) (*bytes.Buffer, process_
8281
return outputBuffer, cmd
8382
}
8483

85-
func subprocessRunning(cmd *exec.Cmd) bool {
86-
if cmd == nil {
87-
return false
88-
}
89-
if cmd.Process == nil {
90-
return false
91-
}
92-
if cmd.ProcessState != nil {
93-
if cmd.ProcessState.Exited() {
94-
return false
95-
}
96-
}
97-
98-
return true
99-
}
100-
101-
func terminate(pg process_group.ProcessGroup) {
84+
func terminate(pg process_group.ProcessGroup, doneChan chan bool) {
10285
pg.Signal(syscall.SIGTERM)
103-
done := make(chan bool, 1)
10486
go func() {
10587
select {
10688
case <-time.After(*waitDuration):
10789
log.Logf("The subprocess wasn't terminated within %s. Forcing to close.", *waitDuration)
10890
kill(pg)
109-
case <-done:
91+
<-doneChan
92+
case <-doneChan:
11093
// The subprocess was terminated with SIGTERM
11194
}
11295
}()
113-
pg.Wait()
114-
done <- true
11596
pg.Close()
11697
}
11798

11899
func kill(pg process_group.ProcessGroup) {
119-
if subprocessRunning(pg.RootProcess()) {
120-
log.Logf("Sending SIGKILL to the subprocess")
121-
pg.Signal(syscall.SIGKILL)
122-
}
100+
log.Log("Sending SIGKILL to the subprocess")
101+
pg.Signal(syscall.SIGKILL)
123102
}

internal/ibazel/command/command_test.go

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ import (
1919
"testing"
2020

2121
"github.com/bazelbuild/bazel-watcher/internal/bazel"
22-
"github.com/bazelbuild/bazel-watcher/internal/ibazel/log"
23-
"github.com/bazelbuild/bazel-watcher/internal/ibazel/process_group"
2422
)
2523

2624
var oldExecCommand = execCommand
@@ -37,37 +35,3 @@ func assertKilled(t *testing.T, cmd *exec.Cmd) {
3735
}
3836
}
3937
}
40-
41-
func TestSubprocessRunning(t *testing.T) {
42-
log.SetLogger(t)
43-
44-
execCommand = func(name string, args ...string) process_group.ProcessGroup {
45-
return oldExecCommand("ls") // Every system has ls.
46-
}
47-
defer func() { execCommand = oldExecCommand }()
48-
49-
if subprocessRunning(nil) {
50-
t.Errorf("Nil subprocesses don't run")
51-
}
52-
53-
cmd := exec.Command("sleep", ".1")
54-
55-
if subprocessRunning(cmd) {
56-
t.Errorf("New subprocess shouldn't have been started yet. State: %v", cmd.ProcessState)
57-
}
58-
59-
if err := cmd.Start(); err != nil {
60-
t.Errorf("cmd.Start(): %v", err)
61-
}
62-
63-
if !subprocessRunning(cmd) {
64-
t.Errorf("New subprocess was never started. State: %v", cmd.ProcessState)
65-
}
66-
67-
err := cmd.Wait()
68-
if err != nil {
69-
t.Errorf("Subprocess finished with error: %v State: %v", err, cmd.ProcessState)
70-
} else if subprocessRunning(cmd) {
71-
t.Errorf("Subprocess still running State: %v", cmd.ProcessState)
72-
}
73-
}

internal/ibazel/command/default_command.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,23 @@ package command
1717
import (
1818
"bytes"
1919
"os"
20+
"os/exec"
2021
"sync"
22+
"sync/atomic"
2123

2224
"github.com/bazelbuild/bazel-watcher/internal/ibazel/log"
2325
"github.com/bazelbuild/bazel-watcher/internal/ibazel/process_group"
2426
)
2527

2628
type defaultCommand struct {
27-
target string
28-
startupArgs []string
29-
bazelArgs []string
30-
args []string
31-
pg process_group.ProcessGroup
32-
termSync sync.Once
29+
target string
30+
startupArgs []string
31+
bazelArgs []string
32+
args []string
33+
pg process_group.ProcessGroup
34+
termSync sync.Once
35+
doneChan chan bool
36+
subprocessRunning atomic.Bool
3337
}
3438

3539
// DefaultCommand is the normal mode of interacting with iBazel. If you start a
@@ -50,13 +54,14 @@ func (c *defaultCommand) Terminate() {
5054
return
5155
}
5256
c.termSync.Do(func() {
53-
terminate(c.pg)
57+
terminate(c.pg, c.doneChan)
5458
})
5559
c.pg = nil
60+
c.subprocessRunning.Store(false)
5661
}
5762

5863
func (c *defaultCommand) Kill() {
59-
if c.pg != nil {
64+
if !c.IsSubprocessRunning() {
6065
kill(c.pg)
6166
}
6267
}
@@ -70,16 +75,35 @@ func (c *defaultCommand) Start() (*bytes.Buffer, error) {
7075
b.WriteToStdout(true)
7176

7277
var outputBuffer *bytes.Buffer
73-
outputBuffer, c.pg = start(b, c.target, c.args)
78+
outputBuffer, pg := start(b, c.target, c.args)
79+
c.pg = pg
80+
doneChan := make(chan bool, 1)
81+
c.doneChan = doneChan
82+
c.subprocessRunning.Store(true)
7483

75-
c.pg.RootProcess().Env = os.Environ()
84+
pg.RootProcess().Env = os.Environ()
7685

7786
var err error
78-
if err = c.pg.Start(); err != nil {
87+
if err = pg.Start(); err != nil {
7988
log.Errorf("Error starting process: %v", err)
8089
return outputBuffer, err
8190
}
8291
log.Log("Starting...")
92+
93+
logf := log.Logf
94+
go func() {
95+
err := pg.Wait()
96+
97+
code := 0
98+
if exiterr, ok := err.(*exec.ExitError); ok {
99+
code = exiterr.ExitCode()
100+
}
101+
logf("Exited (%d)", code)
102+
103+
doneChan <- true
104+
c.subprocessRunning.Store(false)
105+
}()
106+
83107
c.termSync = sync.Once{}
84108
return outputBuffer, nil
85109
}
@@ -91,5 +115,5 @@ func (c *defaultCommand) NotifyOfChanges() *bytes.Buffer {
91115
}
92116

93117
func (c *defaultCommand) IsSubprocessRunning() bool {
94-
return c.pg != nil && subprocessRunning(c.pg.RootProcess())
118+
return c.subprocessRunning.Load()
95119
}

internal/ibazel/command/default_command_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func TestDefaultCommand(t *testing.T) {
5757
}
5858

5959
toKill.Start()
60+
c.subprocessRunning.Store(true)
6061

6162
if !c.IsSubprocessRunning() {
6263
t.Errorf("New subprocess was never started. State: %v", toKill.RootProcess().ProcessState)

internal/ibazel/command/notify_command.go

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,24 @@ import (
1818
"bytes"
1919
"io"
2020
"os"
21+
"os/exec"
2122
"sync"
23+
"sync/atomic"
2224

2325
"github.com/bazelbuild/bazel-watcher/internal/ibazel/log"
2426
"github.com/bazelbuild/bazel-watcher/internal/ibazel/process_group"
2527
)
2628

2729
type notifyCommand struct {
28-
target string
29-
startupArgs []string
30-
bazelArgs []string
31-
args []string
32-
pg process_group.ProcessGroup
33-
stdin io.WriteCloser
34-
termSync sync.Once
30+
target string
31+
startupArgs []string
32+
bazelArgs []string
33+
args []string
34+
pg process_group.ProcessGroup
35+
stdin io.WriteCloser
36+
termSync sync.Once
37+
doneChan chan bool
38+
subprocessRunning atomic.Bool
3539
}
3640

3741
// NotifyCommand is an alternate mode for starting a command. In this mode the
@@ -51,13 +55,14 @@ func (c *notifyCommand) Terminate() {
5155
return
5256
}
5357
c.termSync.Do(func() {
54-
terminate(c.pg)
58+
terminate(c.pg, c.doneChan)
5559
})
5660
c.pg = nil
61+
c.subprocessRunning.Store(false)
5762
}
5863

5964
func (c *notifyCommand) Kill() {
60-
if c.pg != nil {
65+
if !c.IsSubprocessRunning() {
6166
kill(c.pg)
6267
}
6368
}
@@ -71,22 +76,42 @@ func (c *notifyCommand) Start() (*bytes.Buffer, error) {
7176
b.WriteToStdout(true)
7277

7378
var outputBuffer *bytes.Buffer
74-
outputBuffer, c.pg = start(b, c.target, c.args)
79+
outputBuffer, pg := start(b, c.target, c.args)
80+
c.pg = pg
81+
doneChan := make(chan bool, 1)
82+
c.doneChan = doneChan
83+
c.subprocessRunning.Store(true)
84+
7585
// Keep the writer around.
7686
var err error
77-
c.stdin, err = c.pg.RootProcess().StdinPipe()
87+
c.stdin, err = pg.RootProcess().StdinPipe()
7888
if err != nil {
7989
log.Errorf("Error getting stdin pipe: %v", err)
8090
return outputBuffer, err
8191
}
8292

83-
c.pg.RootProcess().Env = append(os.Environ(), "IBAZEL_NOTIFY_CHANGES=y")
93+
pg.RootProcess().Env = append(os.Environ(), "IBAZEL_NOTIFY_CHANGES=y")
8494

85-
if err = c.pg.Start(); err != nil {
95+
if err = pg.Start(); err != nil {
8696
log.Errorf("Error starting process: %v", err)
8797
return outputBuffer, err
8898
}
8999
log.Log("Starting...")
100+
101+
logf := log.Logf
102+
go func() {
103+
err := pg.Wait()
104+
105+
code := 0
106+
if exiterr, ok := err.(*exec.ExitError); ok {
107+
code = exiterr.ExitCode()
108+
}
109+
logf("Exited (%d)", code)
110+
111+
doneChan <- true
112+
c.subprocessRunning.Store(false)
113+
}()
114+
90115
c.termSync = sync.Once{}
91116
return outputBuffer, nil
92117
}
@@ -127,5 +152,5 @@ func (c *notifyCommand) NotifyOfChanges() *bytes.Buffer {
127152
}
128153

129154
func (c *notifyCommand) IsSubprocessRunning() bool {
130-
return c.pg != nil && subprocessRunning(c.pg.RootProcess())
155+
return c.subprocessRunning.Load()
131156
}

0 commit comments

Comments
 (0)