Skip to content

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
keroxp committed May 28, 2024
1 parent bddefa6 commit a53a27f
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 91 deletions.
5 changes: 2 additions & 3 deletions env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ func TestEnsureEnvars(t *testing.T) {
Cluster: "cluster",
Service: "next",
}
if err := EnsureEnvars(e); err != nil {
t.Fatalf(err.Error())
}
err := EnsureEnvars(e)
assert.Errorf(t, err, "--nextTaskDefinitionArn or deploy context must be provided")
})
t.Run("should return err if required props are not defined", func(t *testing.T) {
dummy := "aaa"
Expand Down
72 changes: 34 additions & 38 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"golang.org/x/xerrors"
)

type RunInput struct {
Container *string
Overrides *types.TaskOverride
MaxWait time.Duration
}
type RunResult struct {
ExitCode int32
}

func containerExistsInDefinition(td *types.TaskDefinition, container *string) bool {
func containerExistsInDefinition(td *ecs.RegisterTaskDefinitionInput, container *string) bool {
for _, v := range td.ContainerDefinitions {
if *v.Name == *container {
return true
Expand All @@ -29,13 +31,16 @@ func containerExistsInDefinition(td *types.TaskDefinition, container *string) bo
}

func (c *cage) Run(ctx context.Context, input *RunInput) (*RunResult, error) {
if input.MaxWait == 0 {
input.MaxWait = 5 * time.Minute
}
if !containerExistsInDefinition(c.env.TaskDefinitionInput, input.Container) {
return nil, fmt.Errorf("🚫 '%s' not found in container definitions", *input.Container)
}
td, err := c.CreateNextTaskDefinition(ctx)
if err != nil {
return nil, err
}
if !containerExistsInDefinition(td, input.Container) {
return nil, fmt.Errorf("🚫 '%s' not found in container definitions", *input.Container)
}
o, err := c.ecs.RunTask(ctx, &ecs.RunTaskInput{
Cluster: &c.env.Cluster,
TaskDefinition: td.TaskDefinitionArn,
Expand All @@ -49,42 +54,33 @@ func (c *cage) Run(ctx context.Context, input *RunInput) (*RunResult, error) {
return nil, err
}
taskArn := o.Tasks[0].TaskArn
count := 0
// 5min
maxCount := 30
interval := time.Second * 10
var exitCode int32 = -1
log.Infof("🤖 waiting until task '%s' is running...", *taskArn)
for count < maxCount {
<-c.time.NewTimer(interval).C
o, err := c.ecs.DescribeTasks(ctx, &ecs.DescribeTasksInput{
Cluster: &c.env.Cluster,
Tasks: []string{*taskArn},
})
if err != nil {
return nil, err
}
task := o.Tasks[0]
log.Infof("🤖 task status is '%s'", *task.LastStatus)
if *task.LastStatus != "STOPPED" {
count++
continue
}
for _, container := range task.Containers {
if *container.Name == *input.Container {
if container.ExitCode == nil {
return nil, fmt.Errorf("🚫 container '%s' hasn't exit", *input.Container)
log.Infof("waiting for task '%s' to start...", *taskArn)
if err := ecs.NewTasksRunningWaiter(c.ecs).Wait(ctx, &ecs.DescribeTasksInput{
Cluster: &c.env.Cluster,
Tasks: []string{*taskArn},
}, input.MaxWait); err != nil {
return nil, xerrors.Errorf("task failed to start: %w", err)
}
log.Infof("task '%s' is running", *taskArn)
log.Infof("waiting for task '%s' to stop...", *taskArn)
if result, err := ecs.NewTasksStoppedWaiter(c.ecs).WaitForOutput(ctx, &ecs.DescribeTasksInput{
Cluster: &c.env.Cluster,
Tasks: []string{*taskArn},
}, input.MaxWait); err != nil {
return nil, xerrors.Errorf("task failed to stop: %w", err)
} else {
task := result.Tasks[0]
for _, c := range task.Containers {
if *c.Name == *input.Container {
if c.ExitCode == nil {
return nil, fmt.Errorf("container '%s' hasn't exit", *input.Container)
} else if *c.ExitCode != 0 {
return nil, fmt.Errorf("task exited with %d", *c.ExitCode)
}
exitCode = *container.ExitCode
goto next
return &RunResult{ExitCode: *c.ExitCode}, nil
}
}
return nil, fmt.Errorf("🚫 container '%s' not found in results", *input.Container)
}
return nil, fmt.Errorf("🚫 max attempts exceeded")
next:
if exitCode != 0 {
return nil, fmt.Errorf("🚫 task exited with %d", exitCode)
// Never reached?
return nil, fmt.Errorf("task '%s' not found in result", *taskArn)
}
return &RunResult{ExitCode: exitCode}, nil
}
128 changes: 78 additions & 50 deletions run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,29 @@ import (
)

func TestCage_Run(t *testing.T) {
setupForBasic := func(t *testing.T) (*cage.Envars, *mock_awsiface.MockEcsClient) {
setupForBasic := func(t *testing.T) (*cage.Envars,
*test.MockContext,
*mock_awsiface.MockEcsClient) {
env := test.DefaultEnvars()
mocker := test.NewMockContext()
ctrl := gomock.NewController(t)
ecsMock := mock_awsiface.NewMockEcsClient(ctrl)
ecsMock.EXPECT().RegisterTaskDefinition(gomock.Any(), gomock.Any()).DoAndReturn(mocker.RegisterTaskDefinition).AnyTimes()
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any()).DoAndReturn(mocker.DescribeTasks).AnyTimes()
return env, ecsMock
return env, mocker, ecsMock
}
t.Run("basic", func(t *testing.T) {
overrides := &ecstypes.TaskOverride{}
container := "container"
ctx := context.Background()
env, ecsMock := setupForBasic(t)
ecsMock.EXPECT().RunTask(gomock.Any(), gomock.Any()).DoAndReturn(
func(ctx context.Context, input *ecs.RunTaskInput, optFns ...func(*ecs.Options)) (*ecs.RunTaskOutput, error) {
task, err := mocker.RunTask(ctx, input)
if err != nil {
return nil, err
}
stop, err := mocker.StopTask(ctx, &ecs.StopTaskInput{Cluster: input.Cluster, Task: task.Tasks[0].TaskArn})
if err != nil {
return nil, err
}
return &ecs.RunTaskOutput{Tasks: []ecstypes.Task{*stop.Task}}, nil
},
).AnyTimes()
env, mocker, ecsMock := setupForBasic(t)
gomock.InOrder(
ecsMock.EXPECT().RunTask(gomock.Any(), gomock.Any()).DoAndReturn(mocker.RunTask),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(mocker.DescribeTasks),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, input *ecs.DescribeTasksInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
mocker.StopTask(ctx, &ecs.StopTaskInput{Cluster: &env.Cluster, Task: &input.Tasks[0]})
return mocker.DescribeTasks(ctx, input)
}),
)
cagecli := cage.NewCage(&cage.Input{
Env: env,
ECS: ecsMock,
Expand All @@ -56,86 +52,118 @@ func TestCage_Run(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, result.ExitCode, int32(0))
})
t.Run("should error if max attempts exceeded", func(t *testing.T) {
t.Run("should error if task failed to start", func(t *testing.T) {
overrides := &ecstypes.TaskOverride{}
container := "container"
ctx := context.Background()
env, ecsMock := setupForBasic(t)
ecsMock.EXPECT().DescribeTasks(ctx, gomock.Any()).AnyTimes().Return(&ecs.DescribeTasksOutput{
Tasks: []ecstypes.Task{
{LastStatus: aws.String("RUNNING"),
Containers: []ecstypes.Container{{
Name: &container,
ExitCode: nil,
}}},
},
}, nil)
env, mocker, ecsMock := setupForBasic(t)
gomock.InOrder(
ecsMock.EXPECT().RunTask(gomock.Any(), gomock.Any()).DoAndReturn(mocker.RunTask),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(
func(ctx context.Context, input *ecs.DescribeTasksInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
res, err := mocker.DescribeTasks(ctx, input)
for i := range res.Tasks {
res.Tasks[i].LastStatus = aws.String("PROVISIONING")
}
return res, err
},
),
)
cagecli := cage.NewCage(&cage.Input{
Env: env,
ECS: ecsMock,
Time: test.NewFakeTime(),
})
result, err := cagecli.Run(ctx, &cage.RunInput{
Container: &container,
Overrides: overrides,
MaxWait: 1,
})
assert.Nil(t, result)
assert.EqualError(t, err, "task failed to start: exceeded max wait time for TasksRunning waiter")
})
t.Run("should error if task failed to stop", func(t *testing.T) {
overrides := &ecstypes.TaskOverride{}
container := "container"
ctx := context.Background()
env, mocker, ecsMock := setupForBasic(t)
gomock.InOrder(
ecsMock.EXPECT().RunTask(gomock.Any(), gomock.Any()).DoAndReturn(mocker.RunTask),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(mocker.DescribeTasks).Times(2),
)
cagecli := cage.NewCage(&cage.Input{
Env: env,
ECS: ecsMock,
ALB: nil,
EC2: nil,
Time: test.NewFakeTime(),
})
result, err := cagecli.Run(ctx, &cage.RunInput{
Container: &container,
Overrides: overrides,
MaxWait: 1,
})
assert.Nil(t, result)
assert.EqualError(t, err, "🚫 max attempts exceeded")
assert.EqualError(t, err, "task failed to stop: exceeded max wait time for TasksStopped waiter")
})
t.Run("should error if exit code was not 0", func(t *testing.T) {
overrides := &ecstypes.TaskOverride{}
container := "container"
ctx := context.Background()
env, ecsMock := setupForBasic(t)
env, mocker, ecsMock := setupForBasic(t)
gomock.InOrder(
ecsMock.EXPECT().RunTask(gomock.Any(), gomock.Any()).DoAndReturn(mocker.RunTask),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(mocker.DescribeTasks),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, input *ecs.DescribeTasksInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
stop, _ := mocker.StopTask(ctx, &ecs.StopTaskInput{Cluster: &env.Cluster, Task: &input.Tasks[0]})
for i := range stop.Task.Containers {
stop.Task.Containers[i].ExitCode = aws.Int32(1)
}
return mocker.DescribeTasks(ctx, input)
}),
)
cagecli := cage.NewCage(&cage.Input{
Env: env,
ECS: ecsMock,
ALB: nil,
EC2: nil,
Time: test.NewFakeTime(),
})
result, err := cagecli.Run(ctx, &cage.RunInput{
Container: &container,
Overrides: overrides,
})
assert.Nil(t, result)
assert.EqualError(t, err, "🚫 task exited with 1")
assert.EqualError(t, err, "task exited with 1")
})
t.Run("should error if exit code is nil", func(t *testing.T) {
overrides := &ecstypes.TaskOverride{}
container := "container"
ctx := context.Background()
env, ecsMock := setupForBasic(t)
env, mocker, ecsMock := setupForBasic(t)
gomock.InOrder(
ecsMock.EXPECT().RunTask(gomock.Any(), gomock.Any()).DoAndReturn(mocker.RunTask),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(mocker.DescribeTasks),
ecsMock.EXPECT().DescribeTasks(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, input *ecs.DescribeTasksInput, optFns ...func(*ecs.Options)) (*ecs.DescribeTasksOutput, error) {
stop, _ := mocker.StopTask(ctx, &ecs.StopTaskInput{Cluster: &env.Cluster, Task: &input.Tasks[0]})
for i := range stop.Task.Containers {
stop.Task.Containers[i].ExitCode = nil
}
return mocker.DescribeTasks(ctx, input)
}),
)
cagecli := cage.NewCage(&cage.Input{
Env: env,
ECS: ecsMock,
ALB: nil,
EC2: nil,
Time: test.NewFakeTime(),
})
result, err := cagecli.Run(ctx, &cage.RunInput{
Container: &container,
Overrides: overrides,
})
assert.Nil(t, result)
assert.EqualError(t, err, "🚫 container 'container' hasn't exit")
assert.EqualError(t, err, "container 'container' hasn't exit")
})
t.Run("should error if container doesn't exist in definition", func(t *testing.T) {
overrides := &ecstypes.TaskOverride{}
container := "container"
ctx := context.Background()
env, ecsMock := setupForBasic(t)
td := &ecs.RegisterTaskDefinitionOutput{
TaskDefinition: &ecstypes.TaskDefinition{
ContainerDefinitions: []ecstypes.ContainerDefinition{
{Name: &container},
},
},
}

ecsMock.EXPECT().RegisterTaskDefinition(gomock.Any(), gomock.Any()).Return(td, nil)
env, _, ecsMock := setupForBasic(t)
cagecli := cage.NewCage(&cage.Input{
Env: env,
ECS: ecsMock,
Expand Down

0 comments on commit a53a27f

Please sign in to comment.