Files
Danielle Maywood 92a6d6c2c0 chore: remove unnecessary loop variable captures (#22180)
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.
2026-02-19 09:02:19 +00:00

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
}