mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
92a6d6c2c0
Since Go 1.22, the loop variable capture issue is resolved. Variables declared by for loops are now per-iteration rather than per-loop, making the 'v := v' pattern unnecessary.
297 lines
8.4 KiB
Go
297 lines
8.4 KiB
Go
package dynamicparameters
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"cdr.dev/slog/v3"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/quartz"
|
|
)
|
|
|
|
func TestPartitionEvaluations(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
input int
|
|
expected []int
|
|
}{
|
|
{
|
|
name: "10",
|
|
input: 10,
|
|
expected: []int{5, 3, 1, 1},
|
|
},
|
|
{
|
|
name: "11",
|
|
input: 11,
|
|
expected: []int{6, 3, 1, 1},
|
|
},
|
|
{
|
|
name: "12",
|
|
input: 12,
|
|
expected: []int{6, 3, 2, 1},
|
|
},
|
|
{
|
|
name: "600",
|
|
input: 600,
|
|
expected: []int{300, 150, 75, 38, 19, 9, 5, 2, 1, 1},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got := partitionEvaluations(tc.input)
|
|
require.Equal(t, tc.expected, got)
|
|
total := 0
|
|
for _, v := range got {
|
|
total += v
|
|
}
|
|
require.Equal(t, tc.input, total)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSetupPartitions_TemplateExists(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t).Leveled(slog.LevelDebug)
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
orgID := uuid.New()
|
|
fClient := &fakeClient{
|
|
t: t,
|
|
expectedTemplateName: "test-template",
|
|
expectedOrgID: orgID,
|
|
expectedTags: map[string]string{"foo": "bar"},
|
|
matchedProvisioners: 1,
|
|
templateVersionJobStatus: codersdk.ProvisionerJobSucceeded,
|
|
}
|
|
mClock := quartz.NewMock(t)
|
|
trap := mClock.Trap().TickerFunc("waitForTemplateVersionJobs")
|
|
defer trap.Close()
|
|
uut := partitioner{
|
|
ctx: ctx,
|
|
client: fClient,
|
|
orgID: orgID,
|
|
templateName: "test-template",
|
|
provisionerTags: map[string]string{"foo": "bar"},
|
|
numEvals: 600,
|
|
logger: logger,
|
|
clock: mClock,
|
|
}
|
|
var partitions []Partition
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
var err error
|
|
partitions, err = uut.run()
|
|
errCh <- err
|
|
}()
|
|
trap.MustWait(ctx).MustRelease(ctx)
|
|
mClock.Advance(time.Second * 2).MustWait(ctx)
|
|
err := testutil.RequireReceive(ctx, t, errCh)
|
|
require.NoError(t, err)
|
|
// 600 evaluations should be partitioned into 10 parts: []int{300, 150, 75, 38, 19, 9, 5, 2, 1, 1}
|
|
// c.f. TestPartitionEvaluations. That's 10 template versions and associated uploads.
|
|
require.Equal(t, 10, len(partitions))
|
|
require.Equal(t, 10, fClient.templateVersionsCount)
|
|
require.Equal(t, 10, fClient.uploadsCount)
|
|
require.Equal(t, 1, fClient.templateByNameCount)
|
|
require.Equal(t, 0, fClient.createTemplateCount)
|
|
}
|
|
|
|
func TestSetupPartitions_TemplateDoesntExist(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t).Leveled(slog.LevelDebug)
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
orgID := uuid.New()
|
|
fClient := &fakeClient{
|
|
t: t,
|
|
expectedTemplateName: "test-template",
|
|
expectedOrgID: orgID,
|
|
templateByNameError: codersdk.NewTestError(http.StatusNotFound, "", ""),
|
|
matchedProvisioners: 1,
|
|
templateVersionJobStatus: codersdk.ProvisionerJobSucceeded,
|
|
}
|
|
mClock := quartz.NewMock(t)
|
|
trap := mClock.Trap().TickerFunc("waitForTemplateVersionJobs")
|
|
defer trap.Close()
|
|
uut := partitioner{
|
|
ctx: ctx,
|
|
client: fClient,
|
|
orgID: orgID,
|
|
templateName: "test-template",
|
|
numEvals: 600,
|
|
logger: logger,
|
|
clock: mClock,
|
|
}
|
|
var partitions []Partition
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
var err error
|
|
partitions, err = uut.run()
|
|
errCh <- err
|
|
}()
|
|
trap.MustWait(ctx).MustRelease(ctx)
|
|
mClock.Advance(time.Second * 2).MustWait(ctx)
|
|
err := testutil.RequireReceive(ctx, t, errCh)
|
|
require.NoError(t, err)
|
|
// 600 evaluations should be partitioned into 10 parts: []int{300, 150, 75, 38, 19, 9, 5, 2, 1, 1}
|
|
// c.f. TestPartitionEvaluations. That's 10 template versions and associated uploads.
|
|
require.Equal(t, 10, len(partitions))
|
|
require.Equal(t, 10, fClient.templateVersionsCount)
|
|
require.Equal(t, 10, fClient.uploadsCount)
|
|
require.Equal(t, 1, fClient.templateByNameCount)
|
|
require.Equal(t, 1, fClient.createTemplateCount)
|
|
}
|
|
|
|
func TestSetupPartitions_NoMatchedProvisioners(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t).Leveled(slog.LevelDebug)
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
orgID := uuid.New()
|
|
fClient := &fakeClient{
|
|
t: t,
|
|
expectedTemplateName: "test-template",
|
|
expectedOrgID: orgID,
|
|
matchedProvisioners: 0,
|
|
templateVersionJobStatus: codersdk.ProvisionerJobSucceeded,
|
|
}
|
|
mClock := quartz.NewMock(t)
|
|
uut := partitioner{
|
|
ctx: ctx,
|
|
client: fClient,
|
|
orgID: orgID,
|
|
templateName: "test-template",
|
|
numEvals: 600,
|
|
logger: logger,
|
|
clock: mClock,
|
|
}
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
_, err := uut.run()
|
|
errCh <- err
|
|
}()
|
|
err := testutil.RequireReceive(ctx, t, errCh)
|
|
require.ErrorIs(t, err, ErrNoProvisionersMatched)
|
|
require.Equal(t, 1, fClient.templateVersionsCount)
|
|
require.Equal(t, 1, fClient.uploadsCount)
|
|
require.Equal(t, 1, fClient.templateByNameCount)
|
|
require.Equal(t, 0, fClient.createTemplateCount)
|
|
}
|
|
|
|
func TestSetupPartitions_JobFailed(t *testing.T) {
|
|
t.Parallel()
|
|
logger := testutil.Logger(t).Leveled(slog.LevelDebug)
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
orgID := uuid.New()
|
|
fClient := &fakeClient{
|
|
t: t,
|
|
expectedTemplateName: "test-template",
|
|
expectedOrgID: orgID,
|
|
matchedProvisioners: 1,
|
|
templateVersionJobStatus: codersdk.ProvisionerJobFailed,
|
|
}
|
|
mClock := quartz.NewMock(t)
|
|
trap := mClock.Trap().TickerFunc("waitForTemplateVersionJobs")
|
|
defer trap.Close()
|
|
uut := partitioner{
|
|
ctx: ctx,
|
|
client: fClient,
|
|
orgID: orgID,
|
|
templateName: "test-template",
|
|
numEvals: 600,
|
|
logger: logger,
|
|
clock: mClock,
|
|
}
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
_, err := uut.run()
|
|
errCh <- err
|
|
}()
|
|
trap.MustWait(ctx).MustRelease(ctx)
|
|
mClock.Advance(time.Second * 2).MustWait(ctx)
|
|
err := testutil.RequireReceive(ctx, t, errCh)
|
|
require.ErrorAs(t, err, &ProvisionerJobUnexpectedStatusError{})
|
|
require.Equal(t, 10, fClient.templateVersionsCount)
|
|
require.Equal(t, 10, fClient.uploadsCount)
|
|
require.Equal(t, 1, fClient.templateByNameCount)
|
|
require.Equal(t, 0, fClient.createTemplateCount)
|
|
}
|
|
|
|
type fakeClient struct {
|
|
t testing.TB
|
|
|
|
expectedTemplateName string
|
|
expectedOrgID uuid.UUID
|
|
templateByNameError error
|
|
|
|
expectedTags map[string]string
|
|
matchedProvisioners int
|
|
templateVersionJobStatus codersdk.ProvisionerJobStatus
|
|
|
|
createTemplateCount int
|
|
templateVersionsCount int
|
|
uploadsCount int
|
|
templateByNameCount int
|
|
}
|
|
|
|
func (f *fakeClient) TemplateByName(ctx context.Context, orgID uuid.UUID, templateName string) (codersdk.Template, error) {
|
|
f.templateByNameCount++
|
|
require.Equal(f.t, f.expectedOrgID, orgID)
|
|
require.Equal(f.t, f.expectedTemplateName, templateName)
|
|
|
|
if f.templateByNameError != nil {
|
|
return codersdk.Template{}, f.templateByNameError
|
|
}
|
|
return codersdk.Template{
|
|
ID: uuid.New(),
|
|
Name: f.expectedTemplateName,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeClient) CreateTemplate(ctx context.Context, orgID uuid.UUID, createReq codersdk.CreateTemplateRequest) (codersdk.Template, error) {
|
|
f.createTemplateCount++
|
|
require.Equal(f.t, f.expectedOrgID, orgID)
|
|
require.Equal(f.t, f.expectedTemplateName, createReq.Name)
|
|
|
|
return codersdk.Template{
|
|
ID: uuid.New(),
|
|
Name: f.expectedTemplateName,
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeClient) CreateTemplateVersion(ctx context.Context, orgID uuid.UUID, createReq codersdk.CreateTemplateVersionRequest) (codersdk.TemplateVersion, error) {
|
|
f.templateVersionsCount++
|
|
require.Equal(f.t, f.expectedTags, createReq.ProvisionerTags)
|
|
return codersdk.TemplateVersion{
|
|
ID: uuid.New(),
|
|
Name: f.expectedTemplateName,
|
|
MatchedProvisioners: &codersdk.MatchedProvisioners{Count: f.matchedProvisioners},
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeClient) Upload(ctx context.Context, contentType string, reader io.Reader) (codersdk.UploadResponse, error) {
|
|
f.uploadsCount++
|
|
return codersdk.UploadResponse{
|
|
ID: uuid.New(),
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeClient) TemplateVersion(ctx context.Context, versionID uuid.UUID) (codersdk.TemplateVersion, error) {
|
|
return codersdk.TemplateVersion{
|
|
ID: versionID,
|
|
Job: codersdk.ProvisionerJob{Status: f.templateVersionJobStatus},
|
|
MatchedProvisioners: &codersdk.MatchedProvisioners{Count: f.matchedProvisioners},
|
|
}, nil
|
|
}
|