Skip to content

Commit

Permalink
Merge pull request #21 from codecrafters-io/add-submit-command
Browse files Browse the repository at this point in the history
Refactor test command and add submission handler utility
  • Loading branch information
rohitpaulk authored Apr 30, 2024
2 parents c0d01ee + 4d186c4 commit c96b4d7
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 113 deletions.
10 changes: 7 additions & 3 deletions cmd/codecrafters/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ USAGE
$ codecrafters [command]
EXAMPLES
$ codecrafters test # Run tests without committing changes
$ codecrafters test # Run tests without committing changes
$ codecrafters submit # Commit changes & submit to move to next step
COMMANDS
test: Run tests without committing changes
help: Show usage instructions
test: Run tests without committing changes
submit: Commit changes & submit to move to next step
help: Show usage instructions
VERSION
%s
Expand Down Expand Up @@ -75,6 +77,8 @@ func run() error {
switch cmd {
case "test":
return commands.TestCommand(ctx)
case "submit":
return commands.SubmitCommand(ctx)
case "help",
"": // no argument
flag.Usage()
Expand Down
111 changes: 111 additions & 0 deletions internal/commands/submit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package commands

import (
"context"
"errors"
"fmt"
"os/exec"
"strings"

"github.com/codecrafters-io/cli/internal/utils"
"github.com/getsentry/sentry-go"
"github.com/rs/zerolog"
)

func SubmitCommand(ctx context.Context) (err error) {
logger := zerolog.Ctx(ctx)

logger.Debug().Msg("submit command starts")
defer func() {
logger.Debug().Err(err).Msg("submit command ends")
}()

defer func() {
if p := recover(); p != nil {
logger.Panic().Str("panic", fmt.Sprintf("%v", p)).Stack().Msg("panic")
sentry.CurrentHub().Recover(p)

panic(p)
}

if err == nil {
return
}

var noRepo utils.NoCodecraftersRemoteFoundError
if errors.Is(err, &noRepo) {
// ignore
return
}

sentry.CurrentHub().CaptureException(err)
}()

logger.Debug().Msg("computing repository directory")

repoDir, err := utils.GetRepositoryDir()
if err != nil {
return err
}

logger.Debug().Msgf("found repository directory: %s", repoDir)

logger.Debug().Msg("identifying remotes")

codecraftersRemote, err := utils.IdentifyGitRemote(repoDir)
if err != nil {
return err
}

logger.Debug().Msgf("identified remote: %s, %s", codecraftersRemote.Name, codecraftersRemote.Url)

currentBranchName, err := getCurrentBranch(repoDir)
if err != nil {
return fmt.Errorf("get current branch: %w", err)
}

defaultBranchName := "master" // TODO: Change when we allow customizing the defaultBranch

if currentBranchName != defaultBranchName {
return fmt.Errorf("You need to be on the `%s` branch to run this command.", defaultBranchName)
}

logger.Debug().Msgf("committing changes to %s", defaultBranchName)

commitSha, err := commitChanges(repoDir, "codecrafters submit [skip ci]")
if err != nil {
return fmt.Errorf("commit changes: %w", err)
}

// Place this before the push so that it "feels" fast
fmt.Printf("Submitting changes (commit: %s)...\n", commitSha[:7])

err = pushBranchToRemote(repoDir, codecraftersRemote.Name)
if err != nil {
return fmt.Errorf("push changes: %w", err)
}

logger.Debug().Msgf("pushed changes to remote branch %s", defaultBranchName)

codecraftersClient := utils.NewCodecraftersClient(codecraftersRemote.CodecraftersServerURL())

logger.Debug().Msgf("creating submission for %s", commitSha)

createSubmissionResponse, err := codecraftersClient.CreateSubmission(codecraftersRemote.CodecraftersRepositoryId(), commitSha)
if err != nil {
return fmt.Errorf("create submission: %w", err)
}

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

return utils.HandleSubmission(createSubmissionResponse, ctx, codecraftersClient)
}

func getCurrentBranch(repoDir string) (string, error) {
outputBytes, err := exec.Command("git", "-C", repoDir, "rev-parse", "--abbrev-ref", "HEAD").CombinedOutput()
if err != nil {
return "", wrapError(err, outputBytes, "get current branch")
}

return strings.TrimSpace(string(outputBytes)), nil
}
111 changes: 1 addition & 110 deletions internal/commands/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
Expand All @@ -13,8 +12,6 @@ import (
"time"

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

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

for _, message := range createSubmissionResponse.OnInitMessages {
fmt.Println("")
message.Print()
}

if createSubmissionResponse.BuildLogstreamURL != "" {
logger.Debug().Msgf("streaming build logs from %s", createSubmissionResponse.BuildLogstreamURL)

fmt.Println("")
err = streamLogs(createSubmissionResponse.BuildLogstreamURL)
if err != nil {
return fmt.Errorf("stream build logs: %w", err)
}

logger.Debug().Msg("Finished streaming build logs")
logger.Debug().Msg("fetching build")

fetchBuildResponse, err := codecraftersClient.FetchBuild(createSubmissionResponse.BuildID)
if err != nil {
// TODO: Notify sentry
red := color.New(color.FgRed).SprintFunc()
fmt.Fprintln(os.Stderr, red(err.Error()))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your submission. Please try again?"))
fmt.Fprintln(os.Stderr, red("Let us know at [email protected] if this error persists."))
return err
}

logger.Debug().Msgf("finished fetching build: %v", fetchBuildResponse)
red := color.New(color.FgRed).SprintFunc()

switch fetchBuildResponse.Status {
case "failure":
fmt.Fprintln(os.Stderr, red(""))
fmt.Fprintln(os.Stderr, red("Looks like your codebase failed to build."))
fmt.Fprintln(os.Stderr, red("If you think this is a CodeCrafters error, please let us know at [email protected]."))
fmt.Fprintln(os.Stderr, red(""))
os.Exit(0)
case "success":
time.Sleep(1 * time.Second) // The delay in-between build and test logs is usually 5-10 seconds, so let's buy some time
default:
red := color.New(color.FgRed).SprintFunc()

fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your build. Please try again?"))
fmt.Fprintln(os.Stderr, red("Let us know at [email protected] if this error persists."))
os.Exit(1)
}
}

fmt.Println("")
fmt.Println("Running tests. Logs should appear shortly...")
fmt.Println("")

err = streamLogs(createSubmissionResponse.LogstreamURL)
if err != nil {
return fmt.Errorf("stream logs: %w", err)
}

logger.Debug().Msgf("fetching submission %s", createSubmissionResponse.Id)

fetchSubmissionResponse, err := codecraftersClient.FetchSubmission(createSubmissionResponse.Id)
if err != nil {
// TODO: Notify sentry
red := color.New(color.FgRed).SprintFunc()
fmt.Fprintln(os.Stderr, red(err.Error()))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, red("We couldn't fetch the results of your submission. Please try again?"))
fmt.Fprintln(os.Stderr, red("Let us know at [email protected] if this error persists."))
return err
}

logger.Debug().Msgf("finished fetching submission, status: %s", fetchSubmissionResponse.Status)

switch fetchSubmissionResponse.Status {
case "failure":
for _, message := range createSubmissionResponse.OnFailureMessages {
fmt.Println("")
message.Print()
}
case "success":
for _, message := range createSubmissionResponse.OnSuccessMessages {
fmt.Println("")
message.Print()
}
default:
fmt.Println("")
}

if fetchSubmissionResponse.IsError {
return fmt.Errorf("%s", fetchSubmissionResponse.ErrorMessage)
}

return nil
return utils.HandleSubmission(createSubmissionResponse, ctx, codecraftersClient)
}

func copyRepositoryDirToTempDir(repoDir string) (string, error) {
Expand Down Expand Up @@ -272,20 +177,6 @@ func pushBranchToRemote(tmpDir string, remoteName string) error {
return nil
}

func streamLogs(logstreamUrl string) error {
consumer, err := logstream_consumer.NewConsumer(logstreamUrl, func(message string) {})
if err != nil {
return fmt.Errorf("new log consumer: %w", err)
}

_, err = io.Copy(os.Stdout, consumer)
if err != nil {
return fmt.Errorf("stream data: %w", err)
}

return nil
}

func wrapError(err error, output []byte, msg string) error {
if _, ok := err.(*exec.ExitError); ok {
return fmt.Errorf("add all files: %s", output)
Expand Down
Loading

0 comments on commit c96b4d7

Please sign in to comment.