Skip to content

Commit c96b4d7

Browse files
authored
Merge pull request #21 from codecrafters-io/add-submit-command
Refactor test command and add submission handler utility
2 parents c0d01ee + 4d186c4 commit c96b4d7

File tree

5 files changed

+249
-113
lines changed

5 files changed

+249
-113
lines changed

cmd/codecrafters/main.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ USAGE
2323
$ codecrafters [command]
2424
2525
EXAMPLES
26-
$ codecrafters test # Run tests without committing changes
26+
$ codecrafters test # Run tests without committing changes
27+
$ codecrafters submit # Commit changes & submit to move to next step
2728
2829
COMMANDS
29-
test: Run tests without committing changes
30-
help: Show usage instructions
30+
test: Run tests without committing changes
31+
submit: Commit changes & submit to move to next step
32+
help: Show usage instructions
3133
3234
VERSION
3335
%s
@@ -75,6 +77,8 @@ func run() error {
7577
switch cmd {
7678
case "test":
7779
return commands.TestCommand(ctx)
80+
case "submit":
81+
return commands.SubmitCommand(ctx)
7882
case "help",
7983
"": // no argument
8084
flag.Usage()

internal/commands/submit.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package commands
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/codecrafters-io/cli/internal/utils"
11+
"github.com/getsentry/sentry-go"
12+
"github.com/rs/zerolog"
13+
)
14+
15+
func SubmitCommand(ctx context.Context) (err error) {
16+
logger := zerolog.Ctx(ctx)
17+
18+
logger.Debug().Msg("submit command starts")
19+
defer func() {
20+
logger.Debug().Err(err).Msg("submit command ends")
21+
}()
22+
23+
defer func() {
24+
if p := recover(); p != nil {
25+
logger.Panic().Str("panic", fmt.Sprintf("%v", p)).Stack().Msg("panic")
26+
sentry.CurrentHub().Recover(p)
27+
28+
panic(p)
29+
}
30+
31+
if err == nil {
32+
return
33+
}
34+
35+
var noRepo utils.NoCodecraftersRemoteFoundError
36+
if errors.Is(err, &noRepo) {
37+
// ignore
38+
return
39+
}
40+
41+
sentry.CurrentHub().CaptureException(err)
42+
}()
43+
44+
logger.Debug().Msg("computing repository directory")
45+
46+
repoDir, err := utils.GetRepositoryDir()
47+
if err != nil {
48+
return err
49+
}
50+
51+
logger.Debug().Msgf("found repository directory: %s", repoDir)
52+
53+
logger.Debug().Msg("identifying remotes")
54+
55+
codecraftersRemote, err := utils.IdentifyGitRemote(repoDir)
56+
if err != nil {
57+
return err
58+
}
59+
60+
logger.Debug().Msgf("identified remote: %s, %s", codecraftersRemote.Name, codecraftersRemote.Url)
61+
62+
currentBranchName, err := getCurrentBranch(repoDir)
63+
if err != nil {
64+
return fmt.Errorf("get current branch: %w", err)
65+
}
66+
67+
defaultBranchName := "master" // TODO: Change when we allow customizing the defaultBranch
68+
69+
if currentBranchName != defaultBranchName {
70+
return fmt.Errorf("You need to be on the `%s` branch to run this command.", defaultBranchName)
71+
}
72+
73+
logger.Debug().Msgf("committing changes to %s", defaultBranchName)
74+
75+
commitSha, err := commitChanges(repoDir, "codecrafters submit [skip ci]")
76+
if err != nil {
77+
return fmt.Errorf("commit changes: %w", err)
78+
}
79+
80+
// Place this before the push so that it "feels" fast
81+
fmt.Printf("Submitting changes (commit: %s)...\n", commitSha[:7])
82+
83+
err = pushBranchToRemote(repoDir, codecraftersRemote.Name)
84+
if err != nil {
85+
return fmt.Errorf("push changes: %w", err)
86+
}
87+
88+
logger.Debug().Msgf("pushed changes to remote branch %s", defaultBranchName)
89+
90+
codecraftersClient := utils.NewCodecraftersClient(codecraftersRemote.CodecraftersServerURL())
91+
92+
logger.Debug().Msgf("creating submission for %s", commitSha)
93+
94+
createSubmissionResponse, err := codecraftersClient.CreateSubmission(codecraftersRemote.CodecraftersRepositoryId(), commitSha)
95+
if err != nil {
96+
return fmt.Errorf("create submission: %w", err)
97+
}
98+
99+
logger.Debug().Msgf("submission created: %v", createSubmissionResponse.Id)
100+
101+
return utils.HandleSubmission(createSubmissionResponse, ctx, codecraftersClient)
102+
}
103+
104+
func getCurrentBranch(repoDir string) (string, error) {
105+
outputBytes, err := exec.Command("git", "-C", repoDir, "rev-parse", "--abbrev-ref", "HEAD").CombinedOutput()
106+
if err != nil {
107+
return "", wrapError(err, outputBytes, "get current branch")
108+
}
109+
110+
return strings.TrimSpace(string(outputBytes)), nil
111+
}

internal/commands/test.go

+1-110
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"io"
87
"io/ioutil"
98
"os"
109
"os/exec"
@@ -13,8 +12,6 @@ import (
1312
"time"
1413

1514
"github.com/codecrafters-io/cli/internal/utils"
16-
logstream_consumer "github.com/codecrafters-io/logstream/consumer"
17-
"github.com/fatih/color"
1815
"github.com/getsentry/sentry-go"
1916
cp "github.com/otiai10/copy"
2017
"github.com/rs/zerolog"
@@ -115,99 +112,7 @@ func TestCommand(ctx context.Context) (err error) {
115112

116113
logger.Debug().Msgf("submission created: %v", createSubmissionResponse.Id)
117114

118-
for _, message := range createSubmissionResponse.OnInitMessages {
119-
fmt.Println("")
120-
message.Print()
121-
}
122-
123-
if createSubmissionResponse.BuildLogstreamURL != "" {
124-
logger.Debug().Msgf("streaming build logs from %s", createSubmissionResponse.BuildLogstreamURL)
125-
126-
fmt.Println("")
127-
err = streamLogs(createSubmissionResponse.BuildLogstreamURL)
128-
if err != nil {
129-
return fmt.Errorf("stream build logs: %w", err)
130-
}
131-
132-
logger.Debug().Msg("Finished streaming build logs")
133-
logger.Debug().Msg("fetching build")
134-
135-
fetchBuildResponse, err := codecraftersClient.FetchBuild(createSubmissionResponse.BuildID)
136-
if err != nil {
137-
// TODO: Notify sentry
138-
red := color.New(color.FgRed).SprintFunc()
139-
fmt.Fprintln(os.Stderr, red(err.Error()))
140-
fmt.Fprintln(os.Stderr, "")
141-
fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your submission. Please try again?"))
142-
fmt.Fprintln(os.Stderr, red("Let us know at [email protected] if this error persists."))
143-
return err
144-
}
145-
146-
logger.Debug().Msgf("finished fetching build: %v", fetchBuildResponse)
147-
red := color.New(color.FgRed).SprintFunc()
148-
149-
switch fetchBuildResponse.Status {
150-
case "failure":
151-
fmt.Fprintln(os.Stderr, red(""))
152-
fmt.Fprintln(os.Stderr, red("Looks like your codebase failed to build."))
153-
fmt.Fprintln(os.Stderr, red("If you think this is a CodeCrafters error, please let us know at [email protected]."))
154-
fmt.Fprintln(os.Stderr, red(""))
155-
os.Exit(0)
156-
case "success":
157-
time.Sleep(1 * time.Second) // The delay in-between build and test logs is usually 5-10 seconds, so let's buy some time
158-
default:
159-
red := color.New(color.FgRed).SprintFunc()
160-
161-
fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your build. Please try again?"))
162-
fmt.Fprintln(os.Stderr, red("Let us know at [email protected] if this error persists."))
163-
os.Exit(1)
164-
}
165-
}
166-
167-
fmt.Println("")
168-
fmt.Println("Running tests. Logs should appear shortly...")
169-
fmt.Println("")
170-
171-
err = streamLogs(createSubmissionResponse.LogstreamURL)
172-
if err != nil {
173-
return fmt.Errorf("stream logs: %w", err)
174-
}
175-
176-
logger.Debug().Msgf("fetching submission %s", createSubmissionResponse.Id)
177-
178-
fetchSubmissionResponse, err := codecraftersClient.FetchSubmission(createSubmissionResponse.Id)
179-
if err != nil {
180-
// TODO: Notify sentry
181-
red := color.New(color.FgRed).SprintFunc()
182-
fmt.Fprintln(os.Stderr, red(err.Error()))
183-
fmt.Fprintln(os.Stderr, "")
184-
fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your submission. Please try again?"))
185-
fmt.Fprintln(os.Stderr, red("Let us know at [email protected] if this error persists."))
186-
return err
187-
}
188-
189-
logger.Debug().Msgf("finished fetching submission, status: %s", fetchSubmissionResponse.Status)
190-
191-
switch fetchSubmissionResponse.Status {
192-
case "failure":
193-
for _, message := range createSubmissionResponse.OnFailureMessages {
194-
fmt.Println("")
195-
message.Print()
196-
}
197-
case "success":
198-
for _, message := range createSubmissionResponse.OnSuccessMessages {
199-
fmt.Println("")
200-
message.Print()
201-
}
202-
default:
203-
fmt.Println("")
204-
}
205-
206-
if fetchSubmissionResponse.IsError {
207-
return fmt.Errorf("%s", fetchSubmissionResponse.ErrorMessage)
208-
}
209-
210-
return nil
115+
return utils.HandleSubmission(createSubmissionResponse, ctx, codecraftersClient)
211116
}
212117

213118
func copyRepositoryDirToTempDir(repoDir string) (string, error) {
@@ -272,20 +177,6 @@ func pushBranchToRemote(tmpDir string, remoteName string) error {
272177
return nil
273178
}
274179

275-
func streamLogs(logstreamUrl string) error {
276-
consumer, err := logstream_consumer.NewConsumer(logstreamUrl, func(message string) {})
277-
if err != nil {
278-
return fmt.Errorf("new log consumer: %w", err)
279-
}
280-
281-
_, err = io.Copy(os.Stdout, consumer)
282-
if err != nil {
283-
return fmt.Errorf("stream data: %w", err)
284-
}
285-
286-
return nil
287-
}
288-
289180
func wrapError(err error, output []byte, msg string) error {
290181
if _, ok := err.(*exec.ExitError); ok {
291182
return fmt.Errorf("add all files: %s", output)

0 commit comments

Comments
 (0)