Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@ test_printing_matches_with_grep: build
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"ku5\",\"tester_log_prefix\":\"stage-30\",\"title\":\"Stage #30: Print a single matching line\"},{\"slug\":\"pz6\",\"tester_log_prefix\":\"stage-31\",\"title\":\"Stage #31: Print multiple matching lines\"}]" \
dist/main.out

test_multiple_matches_with_grep: build
CODECRAFTERS_REPOSITORY_DIR=$(shell pwd)/internal/test_helpers/pass_all \
CODECRAFTERS_TEST_CASES_JSON="[{\"slug\":\"cj0\",\"tester_log_prefix\":\"stage-40\",\"title\":\"Stage #40: Print single match\"},{\"slug\":\"ss2\",\"tester_log_prefix\":\"stage-41\",\"title\":\"Stage #41: Print multiple matches\"}, {\"slug\":\"bo4\",\"tester_log_prefix\":\"stage-42\",\"title\":\"Stage #42: Print multiple input lines\"}]" \
dist/main.out

test_all: build
make test_base_with_grep || true
make test_backreferences_with_grep || true
make test_file_search_with_grep || true
make test_quantifiers_with_grep || true
make test_printing_matches_with_grep || true
make test_multiple_matches_with_grep || true

copy_course_file:
hub api \
Expand All @@ -59,4 +65,4 @@ copy_course_file:
> internal/test_helpers/course_definition.yml

update_tester_utils:
go get -u github.com/codecrafters-io/tester-utils
go get -u github.com/codecrafters-io/tester-utils
81 changes: 81 additions & 0 deletions internal/stage_multiple_matches_multiple_lines.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package internal

import (
"fmt"

"github.com/codecrafters-io/grep-tester/internal/test_cases"
"github.com/codecrafters-io/grep-tester/internal/utils"
"github.com/codecrafters-io/tester-utils/random"
"github.com/codecrafters-io/tester-utils/test_case_harness"
)

func testMultipleMatchesMultipleLines(stageHarness *test_case_harness.TestCaseHarness) error {
utils.RelocateSystemGrep(stageHarness)

animals := random.RandomElementsFromArray(utils.ANIMALS, 2)
fruits := random.RandomElementsFromArray(utils.FRUITS, 2)

animal1 := animals[0]
animal2 := animals[1]
fruit1 := fruits[0]
fruit2 := fruits[1]

testCaseCollection := test_cases.PrintMatchesOnlyTestCaseCollection{
{
Pattern: `\d`,
InputLines: []string{
"a1b",
"no digits here",
"2c3d",
},
ExpectedExitCode: 0,
},
{
Pattern: fmt.Sprintf(`(%s|%s)`, fruit1, fruit2),
InputLines: []string{
fmt.Sprintf("I like %s", fruit1),
"nothing here",
fmt.Sprintf("%s and %s are tasty", fruit2, fruit1),
},
ExpectedExitCode: 0,
},
{
Pattern: `XYZ123`,
InputLines: []string{
"abc",
"def",
"ghi",
},
ExpectedExitCode: 1,
},
{
Pattern: `cat`,
InputLines: []string{
"dogdogdog",
"elephant",
"cat-cat-dog",
},
ExpectedExitCode: 0,
},
{
Pattern: fmt.Sprintf(`I saw \d+ (%s|%s)s?`, animal1, animal2),
InputLines: []string{
fmt.Sprintf("Yesterday I saw 3 %ss.", animal1),
"Nothing interesting today.",
fmt.Sprintf("Last week I saw 12 %ss.", animal2),
},
ExpectedExitCode: 0,
},
{
Pattern: `cats and dogs`,
InputLines: []string{
"today is sunny",
"no rains here",
"tomorrow maybe rainy",
},
ExpectedExitCode: 1,
},
}

return testCaseCollection.Run(stageHarness)
}
58 changes: 58 additions & 0 deletions internal/stage_multiple_matches_single_line.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package internal

import (
"fmt"

"github.com/codecrafters-io/grep-tester/internal/test_cases"
"github.com/codecrafters-io/grep-tester/internal/utils"
"github.com/codecrafters-io/tester-utils/random"
"github.com/codecrafters-io/tester-utils/test_case_harness"
)

func testMultipleMatchesSingleLine(stageHarness *test_case_harness.TestCaseHarness) error {
utils.RelocateSystemGrep(stageHarness)

animals := random.RandomElementsFromArray(utils.ANIMALS, 2)
fruits := random.RandomElementsFromArray(utils.FRUITS, 3)

animal1 := animals[0]
animal2 := animals[1]
fruit1 := fruits[0]
fruit2 := fruits[1]
fruit3 := fruits[2]

testCaseCollection := test_cases.PrintMatchesOnlyTestCaseCollection{
{
Pattern: `\d`,
InputLines: []string{"a1b2c3"},
ExpectedExitCode: 0,
},
{
Pattern: fmt.Sprintf(`(%s|%s|%s)`, fruit1, fruit2, fruit3),
InputLines: []string{fmt.Sprintf("%s_%s_%s", fruit3, fruit2, fruit1)},
ExpectedExitCode: 0,
},
{
Pattern: `\d`,
InputLines: []string{"cherry"},
ExpectedExitCode: 1,
},
{
Pattern: `\w\w`,
InputLines: []string{"xx, yy, zz"},
ExpectedExitCode: 0,
},
{
Pattern: `\w`,
InputLines: []string{"##$$%"},
ExpectedExitCode: 1,
},
{
Pattern: fmt.Sprintf(`I see \d+ (%s|%s)s?`, animal1, animal2),
InputLines: []string{fmt.Sprintf("I see 3 %ss. Also, I see 4 %ss.", animal1, animal2)},
ExpectedExitCode: 0,
},
}

return testCaseCollection.Run(stageHarness)
}
50 changes: 50 additions & 0 deletions internal/stage_multiple_matches_single_match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package internal

import (
"fmt"

"github.com/codecrafters-io/grep-tester/internal/test_cases"
"github.com/codecrafters-io/grep-tester/internal/utils"
"github.com/codecrafters-io/tester-utils/test_case_harness"
)

func testMultipleMatchesSingleMatch(stageHarness *test_case_harness.TestCaseHarness) error {
utils.RelocateSystemGrep(stageHarness)

words := utils.RandomWordsWithoutSubstrings(3)

testCaseCollection := test_cases.PrintMatchesOnlyTestCaseCollection{
{
Pattern: `\d`,
InputLines: []string{"only1digit"},
ExpectedExitCode: 0,
},
{
Pattern: `\d`,
InputLines: []string{"cherry"},
ExpectedExitCode: 1,
},
{
Pattern: fmt.Sprintf("^%s", words[0]),
InputLines: []string{words[0] + "_suffix"},
ExpectedExitCode: 0,
},
{
Pattern: fmt.Sprintf("^%s", words[0]),
InputLines: []string{"prefix_" + words[0]},
ExpectedExitCode: 1,
},
{
Pattern: "ca?t",
InputLines: []string{"cat"},
ExpectedExitCode: 0,
},
{
Pattern: `^I see \d+ (cat|dog)s?$`,
InputLines: []string{"I see 42 dogs"},
ExpectedExitCode: 0,
},
}

return testCaseCollection.Run(stageHarness)
}
69 changes: 69 additions & 0 deletions internal/test_cases/print_matches_only_test_case.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package test_cases

import (
"fmt"
"path"
"strings"

"github.com/codecrafters-io/grep-tester/internal/assertions"
"github.com/codecrafters-io/grep-tester/internal/grep"
"github.com/codecrafters-io/tester-utils/test_case_harness"
)

type PrintMatchesOnlyTestCase struct {
Pattern string
InputLines []string
ExpectedExitCode int
}

type PrintMatchesOnlyTestCaseCollection []PrintMatchingLinesTestCase

func (c PrintMatchesOnlyTestCaseCollection) Run(stageHarness *test_case_harness.TestCaseHarness) error {
logger := stageHarness.Logger
executable := stageHarness.Executable

executable.TimeoutInMilliseconds = 1000 * 3600
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A whole 60 seconds? That's kinda wild - is there a reason we need to change this from the default @UdeshyaDhungana?


for _, testCase := range c {
// Run executable and collect result
allInputLines := strings.Join(testCase.InputLines, "\n")
logger.Infof("$ echo -ne %q | ./%s -o -E '%s'", allInputLines, path.Base(executable.Path), testCase.Pattern)

grepResult := grep.EmulateGrep([]string{"-o", "-E", testCase.Pattern}, []byte(allInputLines))
actualResult, err := executable.RunWithStdin([]byte(allInputLines), "-o", "-E", testCase.Pattern)

if err != nil {
return err
}

// Compare against grep
if testCase.ExpectedExitCode != grepResult.ExitCode {
panic(fmt.Sprintf("CodeCrafters Internal Error: Expected exit code %v, grep returned %v", testCase.ExpectedExitCode, grepResult.ExitCode))
}

// Run assertions
exitCodeAssertion := assertions.ExitCodeAssertion{
ExpectedExitCode: testCase.ExpectedExitCode,
}

if err := exitCodeAssertion.Run(actualResult, logger); err != nil {
return err
}

expectedOutput := strings.TrimSpace(string(grepResult.Stdout))

expectedOutputLines := strings.FieldsFunc(expectedOutput, func(r rune) bool {
return r == '\n'
})

orderedLinesAssertion := assertions.OrderedLinesAssertion{
ExpectedOutputLines: expectedOutputLines,
}

if err := orderedLinesAssertion.Run(actualResult, logger); err != nil {
return err
}
}

return nil
}
31 changes: 30 additions & 1 deletion internal/test_helpers/course_definition.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ extensions:

Along the way, you'll learn about extracting matched lines, formatting output, and managing line-by-line processing.

- slug: "multiple-matches"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be done in the challenge PR but noting here: let's order extensions like this:

  • Printing matches
  • Multiple matches
  • Highlighting
  • File search
  • Quantifiers
  • Backreferences

name: "Multiple Matches"
description_markdown: |
In this challenge extension, you'll add support for printing the multiple matching results to your Grep implementation.

Along the way, you'll learn about how to implement the `-o` flag.

stages:
- slug: "cq2"
name: "Match a literal character"
Expand Down Expand Up @@ -324,4 +331,26 @@ stages:
name: "Print multiple matching lines"
difficulty: easy
marketing_md: |-
In this stage, you'll add support for printing multiple input lines if they match the pattern.
In this stage, you'll add support for printing multiple input lines if they match the pattern.

# Multiple matches
- slug: "cj0"
primary_extension_slug: "multiple-matches"
name: "Print single match"
difficulty: medium
marketing_md: |-
In this stage, you'll add support for printing a single matching text to your grep implementation.

- slug: "ss2"
primary_extension_slug: "multiple-matches"
name: "Print multiple matches"
difficulty: medium
marketing_md: |-
In this stage, you'll add support for printing multiple matching texts from a single line to your grep implementation.

- slug: "bo4"
primary_extension_slug: "multiple-matches"
name: "Process multiple input lines"
difficulty: medium
marketing_md: |-
In this stage, you'll add support for processing multiple input lines to print all matching texts.
14 changes: 14 additions & 0 deletions internal/tester_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,19 @@ var testerDefinition = tester_definition.TesterDefinition{
Slug: "pz6",
TestFunc: testPrintingMatchesMultipleLines,
},
// Multiple Matches
{
Slug: "cj0",
TestFunc: testMultipleMatchesSingleMatch,
Timeout: time.Hour,
},
{
Slug: "ss2",
TestFunc: testMultipleMatchesSingleLine,
},
{
Slug: "bo4",
TestFunc: testMultipleMatchesMultipleLines,
},
},
}