Skip to content

Commit 76cd8c3

Browse files
authored
Merge pull request #60 from codecrafters-io/use-actions
Use actions
2 parents e8b272c + 1744df8 commit 76cd8c3

22 files changed

+906
-268
lines changed

cmd/codecrafters/main.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
"context"
54
"flag"
65
"fmt"
76
"os"
@@ -13,6 +12,8 @@ import (
1312

1413
// Usage: codecrafters test
1514
func main() {
15+
utils.InitLogger()
16+
1617
utils.InitSentry()
1718
defer utils.TeardownSentry()
1819

@@ -69,32 +70,27 @@ VERSION
6970
}
7071

7172
func run() error {
72-
ctx := context.Background()
73-
logger := utils.NewLogger()
7473
cmd := flag.Arg(0)
75-
76-
logger.Debug().Msgf("Running command: %s", cmd)
77-
78-
ctx = logger.WithContext(ctx)
74+
utils.Logger.Debug().Msgf("Running command: %s", cmd)
7975

8076
switch cmd {
8177
case "test":
8278
testCmd := flag.NewFlagSet("test", flag.ExitOnError)
8379
shouldTestPrevious := testCmd.Bool("previous", false, "run tests for the current stage and all previous stages in ascending order")
8480
testCmd.Parse(flag.Args()[1:]) // parse the args after the test command
8581

86-
return commands.TestCommand(ctx, *shouldTestPrevious)
82+
return commands.TestCommand(*shouldTestPrevious)
8783
case "submit":
88-
return commands.SubmitCommand(ctx)
84+
return commands.SubmitCommand()
8985
case "task":
9086
taskCmd := flag.NewFlagSet("task", flag.ExitOnError)
9187
stageSlug := taskCmd.String("stage", "", "view instructions for a specific stage (slug, +N, or -N)")
9288
raw := taskCmd.Bool("raw", false, "print instructions without pretty-printing")
9389
taskCmd.Parse(flag.Args()[1:])
9490

95-
return commands.TaskCommand(ctx, *stageSlug, *raw)
91+
return commands.TaskCommand(*stageSlug, *raw)
9692
case "update-buildpack":
97-
return commands.UpdateBuildpackCommand(ctx)
93+
return commands.UpdateBuildpackCommand()
9894
case "help",
9995
"": // no argument
10096
flag.Usage()

internal/actions/action.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package actions
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/codecrafters-io/cli/internal/client"
7+
)
8+
9+
type Action interface {
10+
Execute() error
11+
}
12+
13+
func ActionFromDefinition(actionDefinition client.ActionDefinition) (Action, error) {
14+
switch actionDefinition.Type {
15+
case "await_terminal_build_status":
16+
return NewAwaitTerminalBuildStatusAction(actionDefinition.Args)
17+
case "await_terminal_submission_status":
18+
return NewAwaitTerminalSubmissionStatusAction(actionDefinition.Args)
19+
case "await_terminal_autofix_request_status":
20+
return NewAwaitTerminalAutofixRequestStatusAction(actionDefinition.Args)
21+
case "execute_dynamic_actions":
22+
return NewExecuteDynamicActionsAction(actionDefinition.Args)
23+
case "print_file_diff":
24+
return NewPrintFileDiffAction(actionDefinition.Args)
25+
case "print_message":
26+
return NewPrintMessageAction(actionDefinition.Args)
27+
case "print_terminal_commands_box":
28+
return NewPrintTerminalCommandsBoxAction(actionDefinition.Args)
29+
case "sleep":
30+
return NewSleepAction(actionDefinition.Args)
31+
case "stream_logs":
32+
return NewStreamLogsAction(actionDefinition.Args)
33+
case "terminate":
34+
return NewTerminateAction(actionDefinition.Args)
35+
default:
36+
return nil, fmt.Errorf("unexpected action type: %s", actionDefinition.Type)
37+
}
38+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package actions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"time"
7+
8+
"github.com/codecrafters-io/cli/internal/client"
9+
"github.com/getsentry/sentry-go"
10+
)
11+
12+
type AwaitTerminalAutofixRequestStatusAction struct {
13+
OnFailureActions []Action
14+
OnSuccessActions []Action
15+
SubmissionID string
16+
}
17+
18+
type AwaitTerminalAutofixRequestStatusActionArgs struct {
19+
OnFailureActions []client.ActionDefinition `json:"on_failure_actions"`
20+
OnSuccessActions []client.ActionDefinition `json:"on_success_actions"`
21+
SubmissionID string `json:"submission_id"`
22+
}
23+
24+
func NewAwaitTerminalAutofixRequestStatusAction(argsJson json.RawMessage) (*AwaitTerminalAutofixRequestStatusAction, error) {
25+
var awaitTerminalAutofixRequestStatusActionArgs AwaitTerminalAutofixRequestStatusActionArgs
26+
if err := json.Unmarshal(argsJson, &awaitTerminalAutofixRequestStatusActionArgs); err != nil {
27+
return nil, err
28+
}
29+
30+
onSuccessActions := []Action{}
31+
for _, actionDefinition := range awaitTerminalAutofixRequestStatusActionArgs.OnSuccessActions {
32+
action, err := ActionFromDefinition(actionDefinition)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
onSuccessActions = append(onSuccessActions, action)
38+
}
39+
40+
onFailureActions := []Action{}
41+
for _, actionDefinition := range awaitTerminalAutofixRequestStatusActionArgs.OnFailureActions {
42+
action, err := ActionFromDefinition(actionDefinition)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
onFailureActions = append(onFailureActions, action)
48+
}
49+
50+
return &AwaitTerminalAutofixRequestStatusAction{
51+
OnFailureActions: onFailureActions,
52+
OnSuccessActions: onSuccessActions,
53+
SubmissionID: awaitTerminalAutofixRequestStatusActionArgs.SubmissionID,
54+
}, nil
55+
}
56+
57+
func (a *AwaitTerminalAutofixRequestStatusAction) Execute() error {
58+
attempts := 0
59+
autofixRequestStatus := "in_progress"
60+
61+
// We wait for upto 60 seconds (+ the time it takes to fetch status each time)
62+
for autofixRequestStatus == "in_progress" && attempts < 60 {
63+
var err error
64+
65+
codecraftersClient := client.NewCodecraftersClient()
66+
autofixRequestStatusResponse, err := codecraftersClient.FetchAutofixRequest(a.SubmissionID)
67+
if err != nil {
68+
// We can still proceed here anyway
69+
sentry.CaptureException(err)
70+
} else {
71+
autofixRequestStatus = autofixRequestStatusResponse.Status
72+
}
73+
74+
attempts += 1
75+
time.Sleep(time.Second)
76+
}
77+
78+
switch autofixRequestStatus {
79+
case "success":
80+
for _, action := range a.OnSuccessActions {
81+
if err := action.Execute(); err != nil {
82+
return err
83+
}
84+
}
85+
case "failure":
86+
for _, action := range a.OnFailureActions {
87+
if err := action.Execute(); err != nil {
88+
return err
89+
}
90+
}
91+
default:
92+
err := fmt.Errorf("unexpected autofix request status: %s", autofixRequestStatus)
93+
sentry.CaptureException(err)
94+
95+
PrintMessageAction{Color: "red", Text: "We failed to analyze your test failure in time. Please try again?"}.Execute()
96+
PrintMessageAction{Color: "red", Text: "Let us know at [email protected] if this error persists."}.Execute()
97+
98+
// This is an internal error, let's terminate
99+
TerminateAction{ExitCode: 1}.Execute()
100+
}
101+
102+
return nil
103+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package actions
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"time"
7+
8+
"github.com/codecrafters-io/cli/internal/client"
9+
"github.com/getsentry/sentry-go"
10+
)
11+
12+
type AwaitTerminalBuildStatusAction struct {
13+
BuildID string
14+
OnSuccessActions []Action
15+
OnFailureActions []Action
16+
}
17+
18+
type AwaitTerminalBuildStatusActionArgs struct {
19+
BuildID string `json:"build_id"`
20+
OnSuccessActions []client.ActionDefinition `json:"on_success_actions"`
21+
OnFailureActions []client.ActionDefinition `json:"on_failure_actions"`
22+
}
23+
24+
func NewAwaitTerminalBuildStatusAction(argsJson json.RawMessage) (*AwaitTerminalBuildStatusAction, error) {
25+
var awaitTerminalBuildStatusActionArgs AwaitTerminalBuildStatusActionArgs
26+
if err := json.Unmarshal(argsJson, &awaitTerminalBuildStatusActionArgs); err != nil {
27+
return nil, err
28+
}
29+
30+
onSuccessActions := []Action{}
31+
for _, actionDefinition := range awaitTerminalBuildStatusActionArgs.OnSuccessActions {
32+
action, err := ActionFromDefinition(actionDefinition)
33+
if err != nil {
34+
return nil, err
35+
}
36+
37+
onSuccessActions = append(onSuccessActions, action)
38+
}
39+
40+
onFailureActions := []Action{}
41+
for _, actionDefinition := range awaitTerminalBuildStatusActionArgs.OnFailureActions {
42+
action, err := ActionFromDefinition(actionDefinition)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
onFailureActions = append(onFailureActions, action)
48+
}
49+
50+
return &AwaitTerminalBuildStatusAction{
51+
BuildID: awaitTerminalBuildStatusActionArgs.BuildID,
52+
OnSuccessActions: onSuccessActions,
53+
OnFailureActions: onFailureActions,
54+
}, nil
55+
}
56+
57+
func (a *AwaitTerminalBuildStatusAction) Execute() error {
58+
attempts := 0
59+
buildStatus := "not_started"
60+
61+
// We start waiting for 100 ms, gradually increasing to 2 seconds. Total wait time can be upto 21 seconds ((20 + 21 / 2) * 100ms)
62+
for buildStatus != "success" && buildStatus != "failure" && buildStatus != "error" && attempts < 20 {
63+
var err error
64+
65+
codecraftersClient := client.NewCodecraftersClient()
66+
resp, err := codecraftersClient.FetchBuild(a.BuildID)
67+
if err != nil {
68+
// We can still proceed here anyway
69+
sentry.CaptureException(err)
70+
} else {
71+
buildStatus = resp.Status
72+
}
73+
74+
attempts += 1
75+
time.Sleep(time.Duration(100*attempts) * time.Millisecond)
76+
}
77+
78+
switch buildStatus {
79+
case "success":
80+
for _, action := range a.OnSuccessActions {
81+
if err := action.Execute(); err != nil {
82+
return err
83+
}
84+
}
85+
case "failure":
86+
for _, action := range a.OnFailureActions {
87+
if err := action.Execute(); err != nil {
88+
return err
89+
}
90+
}
91+
default:
92+
err := fmt.Errorf("unexpected build status: %s", buildStatus)
93+
sentry.CaptureException(err)
94+
95+
printErr := PrintMessageAction{Color: "red", Text: "We couldn't fetch the results of your build. Please try again?"}.Execute()
96+
if printErr != nil {
97+
return printErr
98+
}
99+
printErr = PrintMessageAction{Color: "red", Text: "Let us know at [email protected] if this error persists."}.Execute()
100+
if printErr != nil {
101+
return printErr
102+
}
103+
104+
// If the build failed, we don't need to stream test logs
105+
return TerminateAction{ExitCode: 1}.Execute()
106+
}
107+
108+
return nil
109+
}

0 commit comments

Comments
 (0)