-
Notifications
You must be signed in to change notification settings - Fork 5
Implement custom shell command tests and pipeline stages #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 42 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
6304ea8
feat: implement testP1 function for shell command testing
ryan-gang 7284dc8
refactor: improve file writing logic in testP1 function
ryan-gang e14caf7
feat: add testP2 function for custom shell command testing
ryan-gang 2819379
feat: add additional custom commands for shell utilities
ryan-gang dbd8c37
feat: add testP1 and testP2 to tester definition
ryan-gang a7951ca
feat: implement testP3 function for custom shell command testing
ryan-gang 8fea7df
feat: add pipeline stages and implement testP3 function for testing
ryan-gang 467c5d6
feat: update course definition with corrected cat command examples an…
ryan-gang 91ade74
feat: enhance testP3 function to write file content and add prompt as…
ryan-gang 424d86b
feat: enhance testP3 function to append content to file during execut…
ryan-gang 837952f
feat: enhance testP3 function to append additional content and assert…
ryan-gang d1bcbe0
refactor: update how empty lines are handled in singleLineAssertion
ryan-gang 3beaae8
feat: add testP4 function to validate piping from builtins
ryan-gang 0fdc314
feat: add testP5 function to validate piping into builtins
ryan-gang 46377a5
feat: add testP6 function to validate multi command pipelines
ryan-gang 707257e
fix: add panic for missing ExpectedOutput and FallbackPatterns in Sin…
ryan-gang 142a5ce
revert: undo change made to single_line_assertion
ryan-gang b04bd84
refactor: improve file handling in writeFile and appendFile functions
ryan-gang 87b4c78
chore: add tests to tester context
ryan-gang 49163fb
feat: add internal grep implementation
ryan-gang ebd636c
feat: add additional test case for directory listing and file verific…
ryan-gang b62ff1b
refactor: merge stages
ryan-gang 8d83848
refactor: merge stages
ryan-gang 1160437
feat: add grep test and build steps to Makefile
ryan-gang 8dd88a3
refactor: rename pipeline stages and update test function mappings
ryan-gang e65f068
feat: add test cases for bash and ash pipeline stages
ryan-gang 2de9842
fix: update fallback patterns in test case for shell builtin exit
ryan-gang db8a2a5
refactor: remove unused pipeline stage slugs from course definition
ryan-gang ae742f0
fix: update .gitignore to remove trailing slash from .devcontainer
ryan-gang fb7e177
refactor: remove commented-out code in grep test function
ryan-gang 2e4debc
feat: add regex pattern for ls -la output normalization in tests
ryan-gang d621300
Merge branch 'main' into add-pipelines-tests
ryan-gang fff2ad3
fix: ensure ExpectedOutput or fallbackPatterns are provided in Single…
ryan-gang fed0f89
fix: trim newlines from fileContent before splitting into expectedMul…
ryan-gang ab0ac88
feat: add custom executable creation for additional commands in SetUp…
ryan-gang dc6b11d
feat: add function to create grep executable for custom commands
ryan-gang decf73d
fix: improve line reading logic to preserve original line endings in …
ryan-gang 00ad590
fix: adjust expected output formatting for line, word, and byte count…
ryan-gang 7e7f6bd
fix: remove redundant file write operation in testP1 function
ryan-gang dd8567f
test: update fixtures
ryan-gang 255b42e
refactor: remove build step for grep executable in tests
ryan-gang 51adce2
chore: update pipeline stage titles for clarity and improve command m…
ryan-gang 076853e
chore: fix lint issues
ryan-gang 4baeada
fix: correct command syntax in Makefile for testing grep
ryan-gang 4894ec3
chore: update course def
ryan-gang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,5 +11,4 @@ my_exe | |
anticheat | ||
notes.md | ||
shell_notes.md | ||
|
||
.devcontainer |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package custom_executable | ||
|
||
import "fmt" | ||
|
||
func CreateGrepExecutable(outputPath string) error { | ||
err := createExecutableForOSAndArch("grep", outputPath) | ||
if err != nil { | ||
return fmt.Errorf("CodeCrafters Internal Error: creating executable %s failed: %w", "grep", err) | ||
} | ||
return nil | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"os" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
if len(os.Args) < 2 { | ||
fmt.Fprintln(os.Stderr, "Usage: grep PATTERN") | ||
os.Exit(2) // Standard exit code for grep usage error | ||
} | ||
|
||
pattern := os.Args[1] | ||
scanner := bufio.NewScanner(os.Stdin) | ||
found := false | ||
|
||
for scanner.Scan() { | ||
line := scanner.Text() | ||
if strings.Contains(line, pattern) { | ||
fmt.Println(line) | ||
found = true | ||
} | ||
} | ||
|
||
if err := scanner.Err(); err != nil { | ||
fmt.Fprintf(os.Stderr, "grep: error reading input: %v\n", err) | ||
os.Exit(2) | ||
} | ||
|
||
if !found { | ||
os.Exit(1) // Standard exit code for grep when no lines match | ||
} | ||
|
||
os.Exit(0) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
// Pass the -system flag to use system grep instead of custom implementation | ||
// go test ./... -system | ||
// Tests only pass against BSD implementation of grep, not GNU implementation | ||
// Run on darwin only | ||
var useSystemGrep = flag.Bool("system", false, "Use system grep instead of custom implementation") | ||
|
||
func getGrepExecutable(t *testing.T) string { | ||
testerDir := filepath.Join(os.Getenv("TESTER_DIR"), "built_executables") | ||
if *useSystemGrep { | ||
return "grep" | ||
} | ||
|
||
switch runtime.GOOS { | ||
case "darwin": | ||
switch runtime.GOARCH { | ||
case "arm64": | ||
return filepath.Join(testerDir, "grep_darwin_arm64") | ||
case "amd64": | ||
return filepath.Join(testerDir, "grep_darwin_amd64") | ||
} | ||
case "linux": | ||
switch runtime.GOARCH { | ||
case "amd64": | ||
return filepath.Join(testerDir, "grep_linux_amd64") | ||
case "arm64": | ||
return filepath.Join(testerDir, "grep_linux_arm64") | ||
} | ||
} | ||
t.Fatalf("Unsupported OS: %s", runtime.GOOS) | ||
return "" | ||
} | ||
ryan-gang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// runGrep runs the grep executable with given arguments and returns its output and error if any | ||
func runGrep(t *testing.T, stdinContent string, args ...string) (string, string, int, error) { | ||
executable := getGrepExecutable(t) | ||
|
||
t.Helper() | ||
prettyPrintCommand(args) | ||
cmd := exec.Command(executable, args...) | ||
|
||
if stdinContent != "" { | ||
cmd.Stdin = strings.NewReader(stdinContent) | ||
} | ||
|
||
var stdout, stderr bytes.Buffer | ||
cmd.Stdout = &stdout | ||
cmd.Stderr = &stderr | ||
|
||
err := cmd.Run() | ||
|
||
exitCode := 0 | ||
if err != nil { | ||
var exitError *exec.ExitError | ||
if errors.As(err, &exitError) { | ||
exitCode = exitError.ExitCode() | ||
} | ||
} | ||
|
||
return stdout.String(), stderr.String(), exitCode, err // Return err as well | ||
} | ||
|
||
func prettyPrintCommand(args []string) { | ||
// Basic pretty printing, similar to cat/wc tests | ||
displayArgs := make([]string, len(args)) | ||
copy(displayArgs, args) | ||
// Potentially shorten paths or quote arguments if needed here | ||
|
||
out := fmt.Sprintf("=== RUN: > grep %s", strings.Join(displayArgs, " ")) | ||
fmt.Println(out) | ||
} | ||
|
||
// TestGrepStdin tests grep functionality reading from standard input. | ||
func TestGrepStdin(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
pattern string | ||
input string | ||
expectedOut string | ||
expectedErr string | ||
expectedExit int | ||
}{ | ||
{ | ||
name: "Simple match", | ||
pattern: "hello", | ||
input: "hello world\nthis is a test\nhello again", | ||
expectedOut: "hello world\nhello again\n", | ||
expectedErr: "", | ||
expectedExit: 0, | ||
}, | ||
{ | ||
name: "No match", | ||
pattern: "goodbye", | ||
input: "hello world\nthis is a test", | ||
expectedOut: "", | ||
expectedErr: "", | ||
expectedExit: 1, | ||
}, | ||
{ | ||
name: "Empty input", | ||
pattern: "test", | ||
input: "", | ||
expectedOut: "", | ||
expectedErr: "", | ||
expectedExit: 1, | ||
}, | ||
{ | ||
name: "Match on empty string pattern", | ||
pattern: "", // Our simple strings.Contains matches everything | ||
input: "line1\nline2", | ||
expectedOut: "line1\nline2\n", | ||
expectedErr: "", | ||
expectedExit: 0, | ||
// Note: Real grep might error or behave differently with empty pattern | ||
}, | ||
{ | ||
name: "Usage error - no pattern", | ||
pattern: "", // Args will be empty | ||
input: "test", | ||
expectedOut: "", | ||
expectedErr: "Usage: grep PATTERN\n", | ||
expectedExit: 2, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
var args []string | ||
|
||
if *useSystemGrep && tt.name == "Usage error - no pattern" { | ||
return // Skip this test for System grep | ||
} | ||
if tt.name != "Usage error - no pattern" { | ||
args = append(args, tt.pattern) | ||
} // else args remains empty | ||
|
||
stdout, stderr, exitCode, runErr := runGrep(t, tt.input, args...) | ||
|
||
// Check exit code first, as stderr/stdout might be irrelevant if exit code is wrong | ||
if exitCode != tt.expectedExit { | ||
// Include stdout/stderr in the error message for better debugging | ||
t.Errorf("Expected exit code %d, got %d. Stderr: %q, Stdout: %q, RunErr: %v", | ||
tt.expectedExit, exitCode, stderr, stdout, runErr) | ||
} | ||
|
||
// Now check stdout and stderr | ||
if stdout != tt.expectedOut { | ||
t.Errorf("Expected stdout %q, got %q", tt.expectedOut, stdout) | ||
} | ||
|
||
if stderr != tt.expectedErr { | ||
t.Errorf("Expected stderr %q, got %q", tt.expectedErr, stderr) | ||
} | ||
|
||
// Handle expected usage error specifically | ||
if tt.expectedExit == 2 && runErr == nil { | ||
t.Errorf("Expected a non-nil error for usage error case, but got nil") | ||
} else if tt.expectedExit != 2 && runErr != nil { | ||
// If we didn't expect an error exit, but got one, check if it was *exec.ExitError | ||
if _, ok := runErr.(*exec.ExitError); !ok { | ||
// It was some other error during execution | ||
t.Errorf("Command execution failed unexpectedly: %v", runErr) | ||
} | ||
// If it *was* an ExitError, we already checked the exit code above. | ||
} | ||
}) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.