Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit93933d7

Browse files
authored
feat(cli): show queue position during workspace builds (#12606)
1 parentc7597fd commit93933d7

File tree

3 files changed

+174
-30
lines changed

3 files changed

+174
-30
lines changed

‎cli/cliui/provisionerjob.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ func (err *ProvisionerJobError) Error() string {
5454
returnerr.Message
5555
}
5656

57+
const (
58+
ProvisioningStateQueued="Queued"
59+
ProvisioningStateRunning="Running"
60+
)
61+
5762
// ProvisionerJob renders a provisioner job with interactive cancellation.
5863
funcProvisionerJob(ctx context.Context,wr io.Writer,optsProvisionerJobOptions)error {
5964
ifopts.FetchInterval==0 {
@@ -63,8 +68,9 @@ func ProvisionerJob(ctx context.Context, wr io.Writer, opts ProvisionerJobOption
6368
defercancelFunc()
6469

6570
var (
66-
currentStage="Queued"
71+
currentStage=ProvisioningStateQueued
6772
currentStageStartedAt=time.Now().UTC()
73+
currentQueuePos=-1
6874

6975
errChan=make(chanerror,1)
7076
job codersdk.ProvisionerJob
@@ -74,7 +80,20 @@ func ProvisionerJob(ctx context.Context, wr io.Writer, opts ProvisionerJobOption
7480
sw:=&stageWriter{w:wr,verbose:opts.Verbose,silentLogs:opts.Silent}
7581

7682
printStage:=func() {
77-
sw.Start(currentStage)
83+
out:=currentStage
84+
85+
ifcurrentStage==ProvisioningStateQueued&&currentQueuePos>0 {
86+
varqueuePosstring
87+
ifcurrentQueuePos==1 {
88+
queuePos="next"
89+
}else {
90+
queuePos=fmt.Sprintf("position: %d",currentQueuePos)
91+
}
92+
93+
out=pretty.Sprintf(DefaultStyles.Warn,"%s (%s)",currentStage,queuePos)
94+
}
95+
96+
sw.Start(out)
7897
}
7998

8099
updateStage:=func(stagestring,startedAt time.Time) {
@@ -103,15 +122,26 @@ func ProvisionerJob(ctx context.Context, wr io.Writer, opts ProvisionerJobOption
103122
errChan<-xerrors.Errorf("fetch: %w",err)
104123
return
105124
}
125+
ifjob.QueuePosition!=currentQueuePos {
126+
initialState:=currentQueuePos==-1
127+
128+
currentQueuePos=job.QueuePosition
129+
// Print an update when the queue position changes, but:
130+
// - not initially, because the stage is printed at startup
131+
// - not when we're first in the queue, because it's redundant
132+
if!initialState&&currentQueuePos!=0 {
133+
printStage()
134+
}
135+
}
106136
ifjob.StartedAt==nil {
107137
return
108138
}
109-
ifcurrentStage!="Queued" {
139+
ifcurrentStage!=ProvisioningStateQueued {
110140
// If another stage is already running, there's no need
111141
// for us to notify the user we're running!
112142
return
113143
}
114-
updateStage("Running",*job.StartedAt)
144+
updateStage(ProvisioningStateRunning,*job.StartedAt)
115145
}
116146

117147
ifopts.Cancel!=nil {
@@ -143,8 +173,8 @@ func ProvisionerJob(ctx context.Context, wr io.Writer, opts ProvisionerJobOption
143173
}
144174

145175
// The initial stage needs to print after the signal handler has been registered.
146-
printStage()
147176
updateJob()
177+
printStage()
148178

149179
logs,closer,err:=opts.Logs()
150180
iferr!=nil {

‎cli/cliui/provisionerjob_test.go

Lines changed: 116 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package cliui_test
22

33
import (
44
"context"
5+
"fmt"
56
"io"
67
"os"
8+
"regexp"
79
"runtime"
810
"sync"
911
"testing"
1012
"time"
1113

14+
"github.com/coder/coder/v2/testutil"
1215
"github.com/stretchr/testify/assert"
1316

1417
"github.com/coder/coder/v2/cli/cliui"
@@ -25,7 +28,11 @@ func TestProvisionerJob(t *testing.T) {
2528
t.Parallel()
2629

2730
test:=newProvisionerJob(t)
28-
gofunc() {
31+
32+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
33+
defercancel()
34+
35+
testutil.Go(t,func() {
2936
<-test.Next
3037
test.JobMutex.Lock()
3138
test.Job.Status=codersdk.ProvisionerJobRunning
@@ -39,20 +46,26 @@ func TestProvisionerJob(t *testing.T) {
3946
test.Job.CompletedAt=&now
4047
close(test.Logs)
4148
test.JobMutex.Unlock()
42-
}()
43-
test.PTY.ExpectMatch("Queued")
44-
test.Next<-struct{}{}
45-
test.PTY.ExpectMatch("Queued")
46-
test.PTY.ExpectMatch("Running")
47-
test.Next<-struct{}{}
48-
test.PTY.ExpectMatch("Running")
49+
})
50+
testutil.Eventually(ctx,t,func(ctx context.Context) (donebool) {
51+
test.PTY.ExpectMatch(cliui.ProvisioningStateQueued)
52+
test.Next<-struct{}{}
53+
test.PTY.ExpectMatch(cliui.ProvisioningStateQueued)
54+
test.PTY.ExpectMatch(cliui.ProvisioningStateRunning)
55+
test.Next<-struct{}{}
56+
test.PTY.ExpectMatch(cliui.ProvisioningStateRunning)
57+
returntrue
58+
},testutil.IntervalFast)
4959
})
5060

5161
t.Run("Stages",func(t*testing.T) {
5262
t.Parallel()
5363

5464
test:=newProvisionerJob(t)
55-
gofunc() {
65+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
66+
defercancel()
67+
68+
testutil.Go(t,func() {
5669
<-test.Next
5770
test.JobMutex.Lock()
5871
test.Job.Status=codersdk.ProvisionerJobRunning
@@ -70,13 +83,86 @@ func TestProvisionerJob(t *testing.T) {
7083
test.Job.CompletedAt=&now
7184
close(test.Logs)
7285
test.JobMutex.Unlock()
73-
}()
74-
test.PTY.ExpectMatch("Queued")
75-
test.Next<-struct{}{}
76-
test.PTY.ExpectMatch("Queued")
77-
test.PTY.ExpectMatch("Something")
78-
test.Next<-struct{}{}
79-
test.PTY.ExpectMatch("Something")
86+
})
87+
testutil.Eventually(ctx,t,func(ctx context.Context) (donebool) {
88+
test.PTY.ExpectMatch(cliui.ProvisioningStateQueued)
89+
test.Next<-struct{}{}
90+
test.PTY.ExpectMatch(cliui.ProvisioningStateQueued)
91+
test.PTY.ExpectMatch("Something")
92+
test.Next<-struct{}{}
93+
test.PTY.ExpectMatch("Something")
94+
returntrue
95+
},testutil.IntervalFast)
96+
})
97+
98+
t.Run("Queue Position",func(t*testing.T) {
99+
t.Parallel()
100+
101+
stage:=cliui.ProvisioningStateQueued
102+
103+
tests:= []struct {
104+
namestring
105+
queuePosint
106+
expectedstring
107+
}{
108+
{
109+
name:"first",
110+
queuePos:0,
111+
expected:fmt.Sprintf("%s$",stage),
112+
},
113+
{
114+
name:"next",
115+
queuePos:1,
116+
expected:fmt.Sprintf(`%s %s$`,stage,regexp.QuoteMeta("(next)")),
117+
},
118+
{
119+
name:"other",
120+
queuePos:4,
121+
expected:fmt.Sprintf(`%s %s$`,stage,regexp.QuoteMeta("(position: 4)")),
122+
},
123+
}
124+
125+
for_,tc:=rangetests {
126+
tc:=tc
127+
128+
t.Run(tc.name,func(t*testing.T) {
129+
t.Parallel()
130+
131+
test:=newProvisionerJob(t)
132+
test.JobMutex.Lock()
133+
test.Job.QueuePosition=tc.queuePos
134+
test.Job.QueueSize=tc.queuePos
135+
test.JobMutex.Unlock()
136+
137+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
138+
defercancel()
139+
140+
testutil.Go(t,func() {
141+
<-test.Next
142+
test.JobMutex.Lock()
143+
test.Job.Status=codersdk.ProvisionerJobRunning
144+
now:=dbtime.Now()
145+
test.Job.StartedAt=&now
146+
test.JobMutex.Unlock()
147+
<-test.Next
148+
test.JobMutex.Lock()
149+
test.Job.Status=codersdk.ProvisionerJobSucceeded
150+
now=dbtime.Now()
151+
test.Job.CompletedAt=&now
152+
close(test.Logs)
153+
test.JobMutex.Unlock()
154+
})
155+
testutil.Eventually(ctx,t,func(ctx context.Context) (donebool) {
156+
test.PTY.ExpectRegexMatch(tc.expected)
157+
test.Next<-struct{}{}
158+
test.PTY.ExpectMatch(cliui.ProvisioningStateQueued)// step completed
159+
test.PTY.ExpectMatch(cliui.ProvisioningStateRunning)
160+
test.Next<-struct{}{}
161+
test.PTY.ExpectMatch(cliui.ProvisioningStateRunning)
162+
returntrue
163+
},testutil.IntervalFast)
164+
})
165+
}
80166
})
81167

82168
// This cannot be ran in parallel because it uses a signal.
@@ -90,7 +176,11 @@ func TestProvisionerJob(t *testing.T) {
90176
}
91177

92178
test:=newProvisionerJob(t)
93-
gofunc() {
179+
180+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
181+
defercancel()
182+
183+
testutil.Go(t,func() {
94184
<-test.Next
95185
currentProcess,err:=os.FindProcess(os.Getpid())
96186
assert.NoError(t,err)
@@ -103,12 +193,15 @@ func TestProvisionerJob(t *testing.T) {
103193
test.Job.CompletedAt=&now
104194
close(test.Logs)
105195
test.JobMutex.Unlock()
106-
}()
107-
test.PTY.ExpectMatch("Queued")
108-
test.Next<-struct{}{}
109-
test.PTY.ExpectMatch("Gracefully canceling")
110-
test.Next<-struct{}{}
111-
test.PTY.ExpectMatch("Queued")
196+
})
197+
testutil.Eventually(ctx,t,func(ctx context.Context) (donebool) {
198+
test.PTY.ExpectMatch(cliui.ProvisioningStateQueued)
199+
test.Next<-struct{}{}
200+
test.PTY.ExpectMatch("Gracefully canceling")
201+
test.Next<-struct{}{}
202+
test.PTY.ExpectMatch(cliui.ProvisioningStateQueued)
203+
returntrue
204+
},testutil.IntervalFast)
112205
})
113206
}
114207

‎pty/ptytest/ptytest.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"fmt"
88
"io"
9+
"regexp"
910
"runtime"
1011
"strings"
1112
"sync"
@@ -145,16 +146,36 @@ type outExpecter struct {
145146
}
146147

147148
func (e*outExpecter)ExpectMatch(strstring)string {
149+
returne.expectMatchContextFunc(str,e.ExpectMatchContext)
150+
}
151+
152+
func (e*outExpecter)ExpectRegexMatch(strstring)string {
153+
returne.expectMatchContextFunc(str,e.ExpectRegexMatchContext)
154+
}
155+
156+
func (e*outExpecter)expectMatchContextFunc(strstring,fnfunc(ctx context.Context,strstring)string)string {
148157
e.t.Helper()
149158

150159
timeout,cancel:=context.WithTimeout(context.Background(),testutil.WaitMedium)
151160
defercancel()
152161

153-
returne.ExpectMatchContext(timeout,str)
162+
returnfn(timeout,str)
154163
}
155164

156165
// TODO(mafredri): Rename this to ExpectMatch when refactoring.
157166
func (e*outExpecter)ExpectMatchContext(ctx context.Context,strstring)string {
167+
returne.expectMatcherFunc(ctx,str,func(src,patternstring)bool {
168+
returnstrings.Contains(src,pattern)
169+
})
170+
}
171+
172+
func (e*outExpecter)ExpectRegexMatchContext(ctx context.Context,strstring)string {
173+
returne.expectMatcherFunc(ctx,str,func(src,patternstring)bool {
174+
returnregexp.MustCompile(pattern).MatchString(src)
175+
})
176+
}
177+
178+
func (e*outExpecter)expectMatcherFunc(ctx context.Context,strstring,fnfunc(src,patternstring)bool)string {
158179
e.t.Helper()
159180

160181
varbuffer bytes.Buffer
@@ -168,7 +189,7 @@ func (e *outExpecter) ExpectMatchContext(ctx context.Context, str string) string
168189
iferr!=nil {
169190
returnerr
170191
}
171-
ifstrings.Contains(buffer.String(),str) {
192+
iffn(buffer.String(),str) {
172193
returnnil
173194
}
174195
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp