From 6b30e47c1764cd1f98f14207d17192bfd49cad87 Mon Sep 17 00:00:00 2001 From: "CodeCrafters (Bot)" Date: Thu, 29 Aug 2024 16:39:23 +0100 Subject: [PATCH] Initial commit --- .github/workflows/publish.yml | 29 +++++ .github/workflows/test.yml | 24 ++++ .gitignore | 1 + Makefile | 27 ++++ README.md | 15 +++ cmd/tester/main.go | 23 ++++ go.mod | 18 +++ go.sum | 39 ++++++ internal/cli.go | 18 +++ internal/stage_init.go | 39 ++++++ internal/stages_test.go | 25 ++++ internal/test_helpers/course_definition.yml | 120 ++++++++++++++++++ internal/test_helpers/fixtures/init/failure | 6 + .../test_helpers/pass_all/codecrafters.yml | 1 + internal/test_helpers/pass_all/script.sh | 2 + .../scenarios/init/failure/codecrafters.yml | 1 + .../scenarios/init/failure/script.sh | 2 + internal/tester_definition.go | 16 +++ internal/tester_definition_test.go | 11 ++ main.goreleaser.yml | 11 ++ test.sh | 2 + 21 files changed, 430 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/tester/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cli.go create mode 100644 internal/stage_init.go create mode 100644 internal/stages_test.go create mode 100644 internal/test_helpers/course_definition.yml create mode 100644 internal/test_helpers/fixtures/init/failure create mode 100644 internal/test_helpers/pass_all/codecrafters.yml create mode 100755 internal/test_helpers/pass_all/script.sh create mode 100644 internal/test_helpers/scenarios/init/failure/codecrafters.yml create mode 100755 internal/test_helpers/scenarios/init/failure/script.sh create mode 100644 internal/tester_definition.go create mode 100644 internal/tester_definition_test.go create mode 100644 main.goreleaser.yml create mode 100755 test.sh diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9e5c4a3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,29 @@ +name: Publish + +on: + push: + tags: + - "v*" + +jobs: + publish_tester: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.17.x + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v1 + with: + version: v0.177.0 + args: release -f main.goreleaser.yml --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..edd045b --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Test + +on: push + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + lfs: true + + - name: Set up Go + uses: actions/setup-go@v1 + with: + go-version: 1.17.x + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.8' + + - run: make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dist diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..caaed3b --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +.PHONY: release build test test_with_bash copy_course_file + +current_version_number := $(shell git tag --list "v*" | sort -V | tail -n 1 | cut -c 2-) +next_version_number := $(shell echo $$(($(current_version_number)+1))) + +release: + git tag v$(next_version_number) + git push origin main v$(next_version_number) + +build: + go build -o dist/main.out ./cmd/tester + +test: + TESTER_DIR=$(shell pwd) go test -v ./internal/ + +test_and_watch: + onchange '**/*' -- go test -v ./internal/ + +copy_course_file: + hub api \ + repos/codecrafters-io/build-your-own-grep/contents/course-definition.yml \ + | jq -r .content \ + | base64 -d \ + > internal/test_helpers/course_definition.yml + +update_tester_utils: + go get -u github.com/codecrafters-io/tester-utils \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..96183da --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Challenge Tester + +This is a program that validates your progress on the "Build your own grep" challenge. + +## Requirements for binary + +- Following environment variables: + - `CODECRAFTERS_SUBMISSION_DIR` - root of the user's code submission + - `CODECRAFTERS_TEST_CASES_JSON` - test cases in JSON format + +## User code requirements + +- A binary named `.sh` that executes the program. +- A file named `codecrafters.yml`, with the following values: + - `debug` diff --git a/cmd/tester/main.go b/cmd/tester/main.go new file mode 100644 index 0000000..e3be20d --- /dev/null +++ b/cmd/tester/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + "strings" + + "github.com/codecrafters-io/grep-tester/internal" +) + +func main() { + os.Exit(internal.RunCLI(envMap())) +} + +func envMap() map[string]string { + result := make(map[string]string) + for _, keyVal := range os.Environ() { + split := strings.SplitN(keyVal, "=", 2) + key, val := split[0], split[1] + result[key] = val + } + + return result +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7afa0fe --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module github.com/codecrafters-io/grep-tester + +go 1.20 + +require github.com/codecrafters-io/tester-utils v0.2.15 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/sys v0.18.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b29734b --- /dev/null +++ b/go.sum @@ -0,0 +1,39 @@ +github.com/codecrafters-io/tester-utils v0.2.1 h1:+bdZLJhFMWcf+/E3C4NlGsIvxvsyBWlrrqceXRFyu6E= +github.com/codecrafters-io/tester-utils v0.2.1/go.mod h1:BeSLyqBpFxUwIm41QlnuRG7ZsabBXWE2Ga3LMFUXAPM= +github.com/codecrafters-io/tester-utils v0.2.10 h1:Xpu6wzgpRpiZPFNGWMRcJXwAZZ0Yb5Mv6IDOR319f+s= +github.com/codecrafters-io/tester-utils v0.2.10/go.mod h1:BeSLyqBpFxUwIm41QlnuRG7ZsabBXWE2Ga3LMFUXAPM= +github.com/codecrafters-io/tester-utils v0.2.15 h1:d6lbr1nVkZ4+G55E47bG3bf1lWiMLOUZy5EiHwX2SsE= +github.com/codecrafters-io/tester-utils v0.2.15/go.mod h1:BeSLyqBpFxUwIm41QlnuRG7ZsabBXWE2Ga3LMFUXAPM= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/cli.go b/internal/cli.go new file mode 100644 index 0000000..33530b5 --- /dev/null +++ b/internal/cli.go @@ -0,0 +1,18 @@ +package internal + +import ( + "fmt" + + testerutils "github.com/codecrafters-io/tester-utils" +) + +func RunCLI(env map[string]string) int { + tester, err := testerutils.NewTester(env, testerDefinition) + + if err != nil { + fmt.Printf("%s", err) + return 1 + } + + return tester.RunCLI() +} diff --git a/internal/stage_init.go b/internal/stage_init.go new file mode 100644 index 0000000..0a447dd --- /dev/null +++ b/internal/stage_init.go @@ -0,0 +1,39 @@ +package internal + +import ( + "fmt" + + "github.com/codecrafters-io/tester-utils/test_case_harness" +) + +// Example from the grep course +func testInit(stageHarness *test_case_harness.TestCaseHarness) error { + // should exit with 0: echo "dog" | grep -E "d" + stageHarness.Logger.Infof("$ echo \"%s\" | ./script.sh -E \"%s\"", "dog", "d") + + result, err := stageHarness.Executable.RunWithStdin([]byte("dog"), "-E", "d") + if err != nil { + return err + } + + if result.ExitCode != 0 { + return fmt.Errorf("expected exit code %v, got %v", 0, result.ExitCode) + } + + stageHarness.Logger.Successf("✓ Received exit code %d.", 0) + + // should exit with 1: echo "dog" | grep -E "d" + stageHarness.Logger.Infof("$ echo \"%s\" | ./script.sh -E \"%s\"", "dog", "f") + + result, err = stageHarness.Executable.RunWithStdin([]byte("dog"), "-E", "f") + if err != nil { + return err + } + + if result.ExitCode != 1 { + return fmt.Errorf("expected exit code %v, got %v", 1, result.ExitCode) + } + + stageHarness.Logger.Successf("✓ Received exit code %d.", 1) + return nil +} diff --git a/internal/stages_test.go b/internal/stages_test.go new file mode 100644 index 0000000..f523b51 --- /dev/null +++ b/internal/stages_test.go @@ -0,0 +1,25 @@ +package internal + +import ( + "testing" + + tester_utils_testing "github.com/codecrafters-io/tester-utils/testing" +) + +func TestStages(t *testing.T) { + testCases := map[string]tester_utils_testing.TesterOutputTestCase{ + "literal_character": { + UntilStageSlug: "init", + CodePath: "./test_helpers/scenarios/init/failure", + ExpectedExitCode: 1, + StdoutFixturePath: "./test_helpers/fixtures/init/failure", + NormalizeOutputFunc: normalizeTesterOutput, + }, + } + + tester_utils_testing.TestTesterOutput(t, testerDefinition, testCases) +} + +func normalizeTesterOutput(testerOutput []byte) []byte { + return testerOutput +} diff --git a/internal/test_helpers/course_definition.yml b/internal/test_helpers/course_definition.yml new file mode 100644 index 0000000..68963ce --- /dev/null +++ b/internal/test_helpers/course_definition.yml @@ -0,0 +1,120 @@ +# This should be replaced with your course's course-definition.yml + +# Used in your course's URL: https://app.codecrafters.io/courses/ +# Example: "redis" +slug: "your_course_slug" + +# The name of your course. This will be displayed in the course catalog, and on other course pages. +# Example: "Build your own Redis" +name: "Build your own something" + +# A short name for your course, this'll be used in copy like emails. +# Example: "Redis" +short_name: "dummy" + +# The release status for your course. +# +# - alpha: Only visible to yourself and CodeCrafters staff. +# - beta: Visible to all CodeCrafters users, but with a "beta" label. +# - live: Visible to all CodeCrafters users, no label. +# +# Allowed values: "alpha", "beta", "live" +release_status: "alpha" + +# This is shown on the course overview page. Markdown supported, recommended length ~30 words. +# +# Recommended format: +# +# > In this challenge, you'll build ABC that's capable of D, E, F and G. Along the way, we'll learn about X, Y, Z and more. +# +# Example: +# +# > In this challenge, you'll build a toy Redis clone that's capable of handling basic commands like PING, GET +# > and SET. Along the way, we'll learn about event loops, the Redis Protocol and more. +description_md: |- + Add a description for your course here. + +# This is shown on the catalog. Plaintext only, recommended length ~10 words. +# +# Recommended format: +# +# > Learn about X, Y, Z and more +# +# Example: +# +# > Learn about TCP servers, the Redis protocol and more +# +# **TODO**: Remove _md suffix since markdown isn't supported +short_description_md: |- + Add a short description for your course here. + +# The percentage of users who complete your course. We'll calculate this automatically in the future, safe to ignore for now. +completion_percentage: 15 + +# The languages that your course supports. +languages: + - slug: "go" + - slug: "python" + - slug: "rust" + +marketing: + # Shown in the catalog. + # + # Recommended guidelines: + # + # - "easy": < 2h of work for an experienced developer + # - "medium": > 6h of work for an experienced developer + # - "hard": > 6h of work for an experienced developer + # + # Allowed values: "easy", "medium", "hard" + difficulty: medium + + # This is shown as an example when users suggest extensions to your course. + # Example: "Persistence" (from the Redis challenge) + sample_extension_idea_title: "My course extension idea" + + # This is shown as an example when users suggest extensions to your course. + # Example: "A Redis server that can read and write .rdb files" (from the Redis challenge) + sample_extension_idea_description: "A description for my course extension idea" + + # These are some default testimonials that you can use. Feel free to switch these out with your own. + testimonials: + - author_name: "Ananthalakshmi Sankar" + author_description: "Automation Engineer at Apple" + author_avatar: "https://codecrafters.io/images/external/testimonials/oxta.jpeg" + link: "https://github.com/anu294" + text: "There are few sites I like as much that have a step by step guide. The real-time feedback is so good, it's creepy!" + + - author_name: "Patrick Burris" + author_description: "Senior Software Developer, CenturyLink" + author_avatar: "https://codecrafters.io/images/external/testimonials/patrick-burris.jpeg" + link: "https://github.com/Jumballaya" + text: |- + I think the instant feedback right there in the git push is really cool. + Didn't even know that was possible! + +stages: + - slug: "init" + name: "Match a literal character" + difficulty: very_easy + description_md: |- + In this stage, we'll handle the simplest regex possible: a single character. + + **Example:** `a` should match "apple", but not "dog". + + Your program will be executed like this: + + ```bash + $ echo "apple" | ./script.sh -E "a" + ``` + + The `-E` flag instructs `grep` to interprets patterns as extended regular expressions (with support + for metacharacters like `+`, `?` etc.). We'll use this flag in all stages. + + You program must [exit](https://en.wikipedia.org/wiki/Exit_status) with 0 if the character is found, and 1 if not. + marketing_md: |- + In this stage, we'll handle the simplest regex possible: a single character. + + **Example:** + + `a` should match "apple", but not "dog". diff --git a/internal/test_helpers/fixtures/init/failure b/internal/test_helpers/fixtures/init/failure new file mode 100644 index 0000000..656d92e --- /dev/null +++ b/internal/test_helpers/fixtures/init/failure @@ -0,0 +1,6 @@ +[stage-1] Running tests for Stage #1: init +[stage-1] $ echo "dog" | ./script.sh -E "d" +[stage-1] ✓ Received exit code 0. +[stage-1] $ echo "dog" | ./script.sh -E "f" +[stage-1] expected exit code 1, got 0 +[stage-1] Test failed (try setting 'debug: true' in your codecrafters.yml to see more details) diff --git a/internal/test_helpers/pass_all/codecrafters.yml b/internal/test_helpers/pass_all/codecrafters.yml new file mode 100644 index 0000000..a7dc7b9 --- /dev/null +++ b/internal/test_helpers/pass_all/codecrafters.yml @@ -0,0 +1 @@ +debug: false \ No newline at end of file diff --git a/internal/test_helpers/pass_all/script.sh b/internal/test_helpers/pass_all/script.sh new file mode 100755 index 0000000..7d2f525 --- /dev/null +++ b/internal/test_helpers/pass_all/script.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec grep "$@" diff --git a/internal/test_helpers/scenarios/init/failure/codecrafters.yml b/internal/test_helpers/scenarios/init/failure/codecrafters.yml new file mode 100644 index 0000000..a7dc7b9 --- /dev/null +++ b/internal/test_helpers/scenarios/init/failure/codecrafters.yml @@ -0,0 +1 @@ +debug: false \ No newline at end of file diff --git a/internal/test_helpers/scenarios/init/failure/script.sh b/internal/test_helpers/scenarios/init/failure/script.sh new file mode 100755 index 0000000..03538c0 --- /dev/null +++ b/internal/test_helpers/scenarios/init/failure/script.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exit 0 \ No newline at end of file diff --git a/internal/tester_definition.go b/internal/tester_definition.go new file mode 100644 index 0000000..b3f0527 --- /dev/null +++ b/internal/tester_definition.go @@ -0,0 +1,16 @@ +package internal + +import ( + "github.com/codecrafters-io/tester-utils/tester_definition" +) + +var testerDefinition = tester_definition.TesterDefinition{ + AntiCheatTestCases: []tester_definition.TestCase{}, + ExecutableFileName: "script.sh", + TestCases: []tester_definition.TestCase{ + { + Slug: "init", + TestFunc: testInit, + }, + }, +} diff --git a/internal/tester_definition_test.go b/internal/tester_definition_test.go new file mode 100644 index 0000000..ce3b55f --- /dev/null +++ b/internal/tester_definition_test.go @@ -0,0 +1,11 @@ +package internal + +import ( + "testing" + + tester_utils_testing "github.com/codecrafters-io/tester-utils/testing" +) + +func TestStagesMatchYAML(t *testing.T) { + tester_utils_testing.ValidateTesterDefinitionAgainstYAML(t, testerDefinition, "test_helpers/course_definition.yml") +} diff --git a/main.goreleaser.yml b/main.goreleaser.yml new file mode 100644 index 0000000..268d195 --- /dev/null +++ b/main.goreleaser.yml @@ -0,0 +1,11 @@ +builds: + - main: ./cmd/tester/main.go + binary: tester + env: + - CGO_ENABLED=0 + +archives: + - name_template: "{{ .Tag }}_{{ .Os }}_{{ .Arch }}" + format: tar.gz + files: + - test.sh diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..1fcf362 --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec "${TESTER_DIR}/tester"