coder/cli/cliui/provisionerjob_test.go

177 lines
4.1 KiB
Go

package cliui_test
import (
"context"
"io"
"os"
"runtime"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/pty/ptytest"
)
// This cannot be ran in parallel because it uses a signal.
// nolint:tparallel
func TestProvisionerJob(t *testing.T) {
t.Run("NoLogs", func(t *testing.T) {
t.Parallel()
test := newProvisionerJob(t)
go func() {
<-test.Next
test.JobMutex.Lock()
test.Job.Status = codersdk.ProvisionerJobRunning
now := database.Now()
test.Job.StartedAt = &now
test.JobMutex.Unlock()
<-test.Next
test.JobMutex.Lock()
test.Job.Status = codersdk.ProvisionerJobSucceeded
now = database.Now()
test.Job.CompletedAt = &now
close(test.Logs)
test.JobMutex.Unlock()
}()
test.PTY.ExpectMatch("Queued")
test.Next <- struct{}{}
test.PTY.ExpectMatch("Queued")
test.PTY.ExpectMatch("Running")
test.Next <- struct{}{}
test.PTY.ExpectMatch("Running")
})
t.Run("Stages", func(t *testing.T) {
t.Parallel()
test := newProvisionerJob(t)
go func() {
<-test.Next
test.JobMutex.Lock()
test.Job.Status = codersdk.ProvisionerJobRunning
now := database.Now()
test.Job.StartedAt = &now
test.Logs <- codersdk.ProvisionerJobLog{
CreatedAt: database.Now(),
Stage: "Something",
}
test.JobMutex.Unlock()
<-test.Next
test.JobMutex.Lock()
test.Job.Status = codersdk.ProvisionerJobSucceeded
now = database.Now()
test.Job.CompletedAt = &now
close(test.Logs)
test.JobMutex.Unlock()
}()
test.PTY.ExpectMatch("Queued")
test.Next <- struct{}{}
test.PTY.ExpectMatch("Queued")
test.PTY.ExpectMatch("Something")
test.Next <- struct{}{}
test.PTY.ExpectMatch("Something")
})
// This cannot be ran in parallel because it uses a signal.
// nolint:paralleltest
t.Run("Cancel", func(t *testing.T) {
if runtime.GOOS == "windows" {
// Sending interrupt signal isn't supported on Windows!
t.SkipNow()
}
test := newProvisionerJob(t)
go func() {
<-test.Next
currentProcess, err := os.FindProcess(os.Getpid())
assert.NoError(t, err)
err = currentProcess.Signal(os.Interrupt)
assert.NoError(t, err)
<-test.Next
test.JobMutex.Lock()
test.Job.Status = codersdk.ProvisionerJobCanceled
now := database.Now()
test.Job.CompletedAt = &now
close(test.Logs)
test.JobMutex.Unlock()
}()
test.PTY.ExpectMatch("Queued")
test.Next <- struct{}{}
test.PTY.ExpectMatch("Gracefully canceling")
test.Next <- struct{}{}
test.PTY.ExpectMatch("Queued")
})
}
type provisionerJobTest struct {
Next chan struct{}
Job *codersdk.ProvisionerJob
JobMutex *sync.Mutex
Logs chan codersdk.ProvisionerJobLog
PTY *ptytest.PTY
}
func newProvisionerJob(t *testing.T) provisionerJobTest {
job := &codersdk.ProvisionerJob{
Status: codersdk.ProvisionerJobPending,
CreatedAt: database.Now(),
}
jobLock := sync.Mutex{}
logs := make(chan codersdk.ProvisionerJobLog, 1)
cmd := &clibase.Cmd{
Handler: func(inv *clibase.Invocation) error {
return cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{
FetchInterval: time.Millisecond,
Fetch: func() (codersdk.ProvisionerJob, error) {
jobLock.Lock()
defer jobLock.Unlock()
return *job, nil
},
Cancel: func() error {
return nil
},
Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) {
return logs, closeFunc(func() error {
return nil
}), nil
},
})
},
}
inv := cmd.Invoke()
ptty := ptytest.New(t)
ptty.Attach(inv)
done := make(chan struct{})
go func() {
defer close(done)
err := inv.WithContext(context.Background()).Run()
if err != nil {
assert.ErrorIs(t, err, cliui.Canceled)
}
}()
t.Cleanup(func() {
<-done
})
return provisionerJobTest{
Next: make(chan struct{}),
Job: job,
JobMutex: &jobLock,
Logs: logs,
PTY: ptty,
}
}
type closeFunc func() error
func (c closeFunc) Close() error {
return c()
}