Skip to content

Commit 760dc8b

Browse files
fix(agent/agentcontainers): fix TestDevcontainerDiscovery/AutoStart flake (#19179)
Fixes coder/internal#864
1 parent 8b66a5a commit 760dc8b

File tree

1 file changed

+107
-147
lines changed

1 file changed

+107
-147
lines changed

agent/agentcontainers/api_test.go

Lines changed: 107 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ func (f *fakeContainerCLI) ExecAs(ctx context.Context, name, user string, args .
7171
// fakeDevcontainerCLI implements the agentcontainers.DevcontainerCLI
7272
// interface for testing.
7373
type fakeDevcontainerCLI struct {
74+
up func(workspaceFolder, configPath string) (string, error)
7475
upID string
7576
upErr error
7677
upErrC chan func() error // If set, send to return err, close to return upErr.
@@ -79,9 +80,14 @@ type fakeDevcontainerCLI struct {
7980
readConfig agentcontainers.DevcontainerConfig
8081
readConfigErr error
8182
readConfigErrC chan func(envs []string) error
83+
84+
configMap map[string]agentcontainers.DevcontainerConfig // By config path
8285
}
8386

84-
func (f *fakeDevcontainerCLI) Up(ctx context.Context, _, _ string, _ ...agentcontainers.DevcontainerCLIUpOptions) (string, error) {
87+
func (f *fakeDevcontainerCLI) Up(ctx context.Context, workspaceFolder, configPath string, _ ...agentcontainers.DevcontainerCLIUpOptions) (string, error) {
88+
if f.up != nil {
89+
return f.up(workspaceFolder, configPath)
90+
}
8591
if f.upErrC != nil {
8692
select {
8793
case <-ctx.Done():
@@ -109,7 +115,12 @@ func (f *fakeDevcontainerCLI) Exec(ctx context.Context, _, _ string, cmd string,
109115
return f.execErr
110116
}
111117

112-
func (f *fakeDevcontainerCLI) ReadConfig(ctx context.Context, _, _ string, envs []string, _ ...agentcontainers.DevcontainerCLIReadConfigOptions) (agentcontainers.DevcontainerConfig, error) {
118+
func (f *fakeDevcontainerCLI) ReadConfig(ctx context.Context, _, configPath string, envs []string, _ ...agentcontainers.DevcontainerCLIReadConfigOptions) (agentcontainers.DevcontainerConfig, error) {
119+
if f.configMap != nil {
120+
if v, found := f.configMap[configPath]; found {
121+
return v, f.readConfigErr
122+
}
123+
}
113124
if f.readConfigErrC != nil {
114125
select {
115126
case <-ctx.Done():
@@ -3576,193 +3587,112 @@ func TestDevcontainerDiscovery(t *testing.T) {
35763587
name string
35773588
agentDir string
35783589
fs map[string]string
3590+
configMap map[string]agentcontainers.DevcontainerConfig
35793591
expectDevcontainerCount int
3580-
setupMocks func(mDCCLI *acmock.MockDevcontainerCLI)
3592+
expectUpCalledCount int
35813593
}{
35823594
{
35833595
name: "SingleEnabled",
35843596
agentDir: "/home/coder",
35853597
expectDevcontainerCount: 1,
3598+
expectUpCalledCount: 1,
35863599
fs: map[string]string{
35873600
"/home/coder/.git/HEAD": "",
35883601
"/home/coder/.devcontainer/devcontainer.json": "",
35893602
},
3590-
setupMocks: func(mDCCLI *acmock.MockDevcontainerCLI) {
3591-
gomock.InOrder(
3592-
// Given: This dev container has auto start enabled.
3593-
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3594-
"/home/coder",
3595-
"/home/coder/.devcontainer/devcontainer.json",
3596-
[]string{},
3597-
).Return(agentcontainers.DevcontainerConfig{
3598-
Configuration: agentcontainers.DevcontainerConfiguration{
3599-
Customizations: agentcontainers.DevcontainerCustomizations{
3600-
Coder: agentcontainers.CoderCustomization{
3601-
AutoStart: true,
3602-
},
3603+
configMap: map[string]agentcontainers.DevcontainerConfig{
3604+
"/home/coder/.devcontainer/devcontainer.json": {
3605+
Configuration: agentcontainers.DevcontainerConfiguration{
3606+
Customizations: agentcontainers.DevcontainerCustomizations{
3607+
Coder: agentcontainers.CoderCustomization{
3608+
AutoStart: true,
36033609
},
36043610
},
3605-
}, nil),
3606-
3607-
// Then: We expect it to be started.
3608-
mDCCLI.EXPECT().Up(gomock.Any(),
3609-
"/home/coder",
3610-
"/home/coder/.devcontainer/devcontainer.json",
3611-
gomock.Any(),
3612-
).Return("", nil),
3613-
)
3611+
},
3612+
},
36143613
},
36153614
},
36163615
{
36173616
name: "SingleDisabled",
36183617
agentDir: "/home/coder",
36193618
expectDevcontainerCount: 1,
3619+
expectUpCalledCount: 0,
36203620
fs: map[string]string{
36213621
"/home/coder/.git/HEAD": "",
36223622
"/home/coder/.devcontainer/devcontainer.json": "",
36233623
},
3624-
setupMocks: func(mDCCLI *acmock.MockDevcontainerCLI) {
3625-
gomock.InOrder(
3626-
// Given: This dev container has auto start disabled.
3627-
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3628-
"/home/coder",
3629-
"/home/coder/.devcontainer/devcontainer.json",
3630-
[]string{},
3631-
).Return(agentcontainers.DevcontainerConfig{
3632-
Configuration: agentcontainers.DevcontainerConfiguration{
3633-
Customizations: agentcontainers.DevcontainerCustomizations{
3634-
Coder: agentcontainers.CoderCustomization{
3635-
AutoStart: false,
3636-
},
3624+
configMap: map[string]agentcontainers.DevcontainerConfig{
3625+
"/home/coder/.devcontainer/devcontainer.json": {
3626+
Configuration: agentcontainers.DevcontainerConfiguration{
3627+
Customizations: agentcontainers.DevcontainerCustomizations{
3628+
Coder: agentcontainers.CoderCustomization{
3629+
AutoStart: false,
36373630
},
36383631
},
3639-
}, nil),
3640-
3641-
// Then: We expect it to _not_ be started.
3642-
mDCCLI.EXPECT().Up(gomock.Any(),
3643-
"/home/coder",
3644-
"/home/coder/.devcontainer/devcontainer.json",
3645-
gomock.Any(),
3646-
).Return("", nil).Times(0),
3647-
)
3632+
},
3633+
},
36483634
},
36493635
},
36503636
{
36513637
name: "OneEnabledOneDisabled",
36523638
agentDir: "/home/coder",
36533639
expectDevcontainerCount: 2,
3640+
expectUpCalledCount: 1,
36543641
fs: map[string]string{
36553642
"/home/coder/.git/HEAD": "",
36563643
"/home/coder/.devcontainer/devcontainer.json": "",
36573644
"/home/coder/project/.devcontainer.json": "",
36583645
},
3659-
setupMocks: func(mDCCLI *acmock.MockDevcontainerCLI) {
3660-
gomock.InOrder(
3661-
// Given: This dev container has auto start enabled.
3662-
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3663-
"/home/coder",
3664-
"/home/coder/.devcontainer/devcontainer.json",
3665-
[]string{},
3666-
).Return(agentcontainers.DevcontainerConfig{
3667-
Configuration: agentcontainers.DevcontainerConfiguration{
3668-
Customizations: agentcontainers.DevcontainerCustomizations{
3669-
Coder: agentcontainers.CoderCustomization{
3670-
AutoStart: true,
3671-
},
3646+
configMap: map[string]agentcontainers.DevcontainerConfig{
3647+
"/home/coder/.devcontainer/devcontainer.json": {
3648+
Configuration: agentcontainers.DevcontainerConfiguration{
3649+
Customizations: agentcontainers.DevcontainerCustomizations{
3650+
Coder: agentcontainers.CoderCustomization{
3651+
AutoStart: true,
36723652
},
36733653
},
3674-
}, nil),
3675-
3676-
// Then: We expect it to be started.
3677-
mDCCLI.EXPECT().Up(gomock.Any(),
3678-
"/home/coder",
3679-
"/home/coder/.devcontainer/devcontainer.json",
3680-
gomock.Any(),
3681-
).Return("", nil),
3682-
)
3683-
3684-
gomock.InOrder(
3685-
// Given: This dev container has auto start disabled.
3686-
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3687-
"/home/coder/project",
3688-
"/home/coder/project/.devcontainer.json",
3689-
[]string{},
3690-
).Return(agentcontainers.DevcontainerConfig{
3691-
Configuration: agentcontainers.DevcontainerConfiguration{
3692-
Customizations: agentcontainers.DevcontainerCustomizations{
3693-
Coder: agentcontainers.CoderCustomization{
3694-
AutoStart: false,
3695-
},
3654+
},
3655+
},
3656+
"/home/coder/project/.devcontainer.json": {
3657+
Configuration: agentcontainers.DevcontainerConfiguration{
3658+
Customizations: agentcontainers.DevcontainerCustomizations{
3659+
Coder: agentcontainers.CoderCustomization{
3660+
AutoStart: false,
36963661
},
36973662
},
3698-
}, nil),
3699-
3700-
// Then: We expect it to _not_ be started.
3701-
mDCCLI.EXPECT().Up(gomock.Any(),
3702-
"/home/coder/project",
3703-
"/home/coder/project/.devcontainer.json",
3704-
gomock.Any(),
3705-
).Return("", nil).Times(0),
3706-
)
3663+
},
3664+
},
37073665
},
37083666
},
37093667
{
37103668
name: "MultipleEnabled",
37113669
agentDir: "/home/coder",
37123670
expectDevcontainerCount: 2,
3671+
expectUpCalledCount: 2,
37133672
fs: map[string]string{
37143673
"/home/coder/.git/HEAD": "",
37153674
"/home/coder/.devcontainer/devcontainer.json": "",
37163675
"/home/coder/project/.devcontainer.json": "",
37173676
},
3718-
setupMocks: func(mDCCLI *acmock.MockDevcontainerCLI) {
3719-
gomock.InOrder(
3720-
// Given: This dev container has auto start enabled.
3721-
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3722-
"/home/coder",
3723-
"/home/coder/.devcontainer/devcontainer.json",
3724-
[]string{},
3725-
).Return(agentcontainers.DevcontainerConfig{
3726-
Configuration: agentcontainers.DevcontainerConfiguration{
3727-
Customizations: agentcontainers.DevcontainerCustomizations{
3728-
Coder: agentcontainers.CoderCustomization{
3729-
AutoStart: true,
3730-
},
3677+
configMap: map[string]agentcontainers.DevcontainerConfig{
3678+
"/home/coder/.devcontainer/devcontainer.json": {
3679+
Configuration: agentcontainers.DevcontainerConfiguration{
3680+
Customizations: agentcontainers.DevcontainerCustomizations{
3681+
Coder: agentcontainers.CoderCustomization{
3682+
AutoStart: true,
37313683
},
37323684
},
3733-
}, nil),
3734-
3735-
// Then: We expect it to be started.
3736-
mDCCLI.EXPECT().Up(gomock.Any(),
3737-
"/home/coder",
3738-
"/home/coder/.devcontainer/devcontainer.json",
3739-
gomock.Any(),
3740-
).Return("", nil),
3741-
)
3742-
3743-
gomock.InOrder(
3744-
// Given: This dev container has auto start enabled.
3745-
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3746-
"/home/coder/project",
3747-
"/home/coder/project/.devcontainer.json",
3748-
[]string{},
3749-
).Return(agentcontainers.DevcontainerConfig{
3750-
Configuration: agentcontainers.DevcontainerConfiguration{
3751-
Customizations: agentcontainers.DevcontainerCustomizations{
3752-
Coder: agentcontainers.CoderCustomization{
3753-
AutoStart: true,
3754-
},
3685+
},
3686+
},
3687+
"/home/coder/project/.devcontainer.json": {
3688+
Configuration: agentcontainers.DevcontainerConfiguration{
3689+
Customizations: agentcontainers.DevcontainerCustomizations{
3690+
Coder: agentcontainers.CoderCustomization{
3691+
AutoStart: true,
37553692
},
37563693
},
3757-
}, nil),
3758-
3759-
// Then: We expect it to be started.
3760-
mDCCLI.EXPECT().Up(gomock.Any(),
3761-
"/home/coder/project",
3762-
"/home/coder/project/.devcontainer.json",
3763-
gomock.Any(),
3764-
).Return("", nil),
3765-
)
3694+
},
3695+
},
37663696
},
37673697
},
37683698
}
@@ -3775,43 +3705,73 @@ func TestDevcontainerDiscovery(t *testing.T) {
37753705
ctx = testutil.Context(t, testutil.WaitShort)
37763706
logger = testutil.Logger(t)
37773707
mClock = quartz.NewMock(t)
3778-
mDCCLI = acmock.NewMockDevcontainerCLI(gomock.NewController(t))
3708+
3709+
upCalledMu sync.Mutex
3710+
upCalledFor = map[string]bool{}
3711+
3712+
fCCLI = &fakeContainerCLI{}
3713+
fDCCLI = &fakeDevcontainerCLI{
3714+
configMap: tt.configMap,
3715+
up: func(_, configPath string) (string, error) {
3716+
upCalledMu.Lock()
3717+
upCalledFor[configPath] = true
3718+
upCalledMu.Unlock()
3719+
return "", nil
3720+
},
3721+
}
37793722

37803723
r = chi.NewRouter()
37813724
)
37823725

3783-
// Given: We setup our mocks. These mocks handle our expectations for these
3784-
// tests. If there are missing/unexpected mock calls, the test will fail.
3785-
tt.setupMocks(mDCCLI)
3786-
37873726
api := agentcontainers.NewAPI(logger,
37883727
agentcontainers.WithClock(mClock),
37893728
agentcontainers.WithWatcher(watcher.NewNoop()),
37903729
agentcontainers.WithFileSystem(initFS(t, tt.fs)),
37913730
agentcontainers.WithManifestInfo("owner", "workspace", "parent-agent", "/home/coder"),
3792-
agentcontainers.WithContainerCLI(&fakeContainerCLI{}),
3793-
agentcontainers.WithDevcontainerCLI(mDCCLI),
3731+
agentcontainers.WithContainerCLI(fCCLI),
3732+
agentcontainers.WithDevcontainerCLI(fDCCLI),
37943733
agentcontainers.WithProjectDiscovery(true),
37953734
agentcontainers.WithDiscoveryAutostart(true),
37963735
)
37973736
api.Start()
3798-
defer api.Close()
37993737
r.Mount("/", api.Routes())
38003738

3801-
// When: All expected dev containers have been found.
3739+
// Given: We allow the discover routing to progress
3740+
var got codersdk.WorkspaceAgentListContainersResponse
38023741
require.Eventuallyf(t, func() bool {
38033742
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
38043743
rec := httptest.NewRecorder()
38053744
r.ServeHTTP(rec, req)
38063745

3807-
got := codersdk.WorkspaceAgentListContainersResponse{}
3746+
got = codersdk.WorkspaceAgentListContainersResponse{}
38083747
err := json.NewDecoder(rec.Body).Decode(&got)
38093748
require.NoError(t, err)
38103749

3811-
return len(got.Devcontainers) >= tt.expectDevcontainerCount
3750+
upCalledMu.Lock()
3751+
upCalledCount := len(upCalledFor)
3752+
upCalledMu.Unlock()
3753+
3754+
return len(got.Devcontainers) >= tt.expectDevcontainerCount && upCalledCount >= tt.expectUpCalledCount
38123755
}, testutil.WaitShort, testutil.IntervalFast, "dev containers never found")
38133756

3814-
// Then: We expect the mock infra to not fail.
3757+
// Close the API. We expect this not to fail because we should have finished
3758+
// at this point.
3759+
err := api.Close()
3760+
require.NoError(t, err)
3761+
3762+
// Then: We expect to find the expected devcontainers
3763+
assert.Len(t, got.Devcontainers, tt.expectDevcontainerCount)
3764+
3765+
// And: We expect `up` to have been called the expected amount of times.
3766+
assert.Len(t, upCalledFor, tt.expectUpCalledCount)
3767+
3768+
// And: `up` was called on the correct containers
3769+
for configPath, config := range tt.configMap {
3770+
autoStart := config.Configuration.Customizations.Coder.AutoStart
3771+
wasUpCalled := upCalledFor[configPath]
3772+
3773+
require.Equal(t, autoStart, wasUpCalled)
3774+
}
38153775
})
38163776
}
38173777

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy