Skip to content
Merged
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
2 changes: 1 addition & 1 deletion internal/stage_pex2.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func testPEX2(stageHarness *test_case_harness.TestCaseHarness) error {

missingVariable := "missing_variable_" + strconv.Itoa(random.RandomInt(1, 100))

testCase := test_cases.DeclarePrintErrorTestCase{
testCase := test_cases.DeclarePrintMissingVariableTestCase{
Variable: missingVariable,
}

Expand Down
4 changes: 2 additions & 2 deletions internal/stage_pex3.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func testPEX3(stageHarness *test_case_harness.TestCaseHarness) error {
}

missingVariable := "missing_" + random.RandomWords(1)[0]
printErrorTestCase := test_cases.DeclarePrintErrorTestCase{Variable: missingVariable}
if err := printErrorTestCase.Run(asserter, shell, logger); err != nil {
printMissingVariableTestCase := test_cases.DeclarePrintMissingVariableTestCase{Variable: missingVariable}
if err := printMissingVariableTestCase.Run(asserter, shell, logger); err != nil {
return err
}

Expand Down
20 changes: 13 additions & 7 deletions internal/stage_pex4.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,33 @@ func testPEX4(stageHarness *test_case_harness.TestCaseHarness) error {
return err
}

words := random.RandomWords(4)
words := random.RandomWords(6)

// Valid assignment: underscore-prefixed name
// Valid assignment: name starting with an underscore
validVariable := "_" + words[0]
validVariableValue := words[1]
if err := (test_cases.DeclareAssignmentTestCase{Variable: validVariable, Value: validVariableValue}).Run(asserter, shell, logger); err != nil {
underscorePrefixedAssignment := test_cases.DeclareAssignmentTestCase{Variable: validVariable, Value: validVariableValue}
if err := underscorePrefixedAssignment.Run(asserter, shell, logger); err != nil {
return err
}

// Invalid assignment: name starting with a digit
invalidVariable := fmt.Sprintf("%d%s", random.RandomInt(2, 9), words[2])
invalidDigitPrefixedVariable := fmt.Sprintf("%d%s", random.RandomInt(2, 9), words[2])
invalidVariableValue := words[3]
if err := (test_cases.DeclareAssignmentTestCase{Variable: invalidVariable, Value: invalidVariableValue}).Run(asserter, shell, logger); err != nil {
digitPrefixedAssignment := test_cases.DeclareAssignmentTestCase{Variable: invalidDigitPrefixedVariable, Value: invalidVariableValue}
if err := digitPrefixedAssignment.Run(asserter, shell, logger); err != nil {
return err
}

if err := (test_cases.DeclarePrintTestCase{Variable: validVariable, Value: validVariableValue}).Run(asserter, shell, logger); err != nil {
// Invalid assignment: name with a hyphen in the middle
hyphenInMiddleVariable := words[4] + "-" + words[5]
hyphenInMiddleAssignment := test_cases.DeclareAssignmentTestCase{Variable: hyphenInMiddleVariable, Value: validVariableValue}
if err := hyphenInMiddleAssignment.Run(asserter, shell, logger); err != nil {
return err
}

if err := (test_cases.DeclarePrintErrorTestCase{Variable: invalidVariable}).Run(asserter, shell, logger); err != nil {
printValidVariable := test_cases.DeclarePrintTestCase{Variable: validVariable, Value: validVariableValue}
if err := printValidVariable.Run(asserter, shell, logger); err != nil {
return err
}

Expand Down
30 changes: 18 additions & 12 deletions internal/stage_pex5.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ func testPEX5(stageHarness *test_case_harness.TestCaseHarness) error {
}

// Declare two variables with valid names and random values
words := random.RandomWords(4)
integerSuffixes := random.RandomInts(1, 10, 2)
words := random.RandomWords(5)
integerSuffixes := random.RandomInts(0, 10, 2)
variableName1 := fmt.Sprintf(
"%s_%d",
strings.ToUpper(words[0][:1])+words[0][1:],
Expand All @@ -46,32 +46,38 @@ func testPEX5(stageHarness *test_case_harness.TestCaseHarness) error {
strings.ToUpper(words[1][:1])+words[1][1:],
integerSuffixes[1],
)
varValue1 := words[2]
varValue2 := words[3]
variableValue1 := words[2]
variableValue2 := words[3]
literalPrefix := words[4]

if err := (test_cases.DeclareAssignmentTestCase{Variable: variableName1, Value: varValue1}).Run(asserter, shell, logger); err != nil {
assignVariable1 := test_cases.DeclareAssignmentTestCase{Variable: variableName1, Value: variableValue1}
if err := assignVariable1.Run(asserter, shell, logger); err != nil {
return err
}
if err := (test_cases.DeclareAssignmentTestCase{Variable: variableName2, Value: varValue2}).Run(asserter, shell, logger); err != nil {

assignVariable2 := test_cases.DeclareAssignmentTestCase{Variable: variableName2, Value: variableValue2}
if err := assignVariable2.Run(asserter, shell, logger); err != nil {
return err
}

// Run the executable with $VAR1 $VAR2 — shell must expand before passing args
command := fmt.Sprintf("%s $%s $%s", executableName, variableName1, variableName2)
// Run the executable with:
// $VAR1 — standalone expansion
// literalPrefix_$VAR2 — expansion embedded mid-word (no braces)
command := fmt.Sprintf("%s $%s %s_$%s", executableName, variableName1, literalPrefix, variableName2)
expectedLines := []string{
"Program was passed 3 args (including program name).",
fmt.Sprintf("Arg #0 (program name): %s", executableName),
fmt.Sprintf("Arg #1: %s", varValue1),
fmt.Sprintf("Arg #2: %s", varValue2),
fmt.Sprintf("Arg #1: %s", variableValue1),
fmt.Sprintf("Arg #2: %s_%s", literalPrefix, variableValue2),
fmt.Sprintf("Program Signature: %s", commandMetadata),
}

testCase := test_cases.CommandWithMultilineResponseTestCase{
bareExpansionCall := test_cases.CommandWithMultilineResponseTestCase{
Command: command,
MultiLineAssertion: assertions.NewMultiLineAssertion(expectedLines),
SuccessMessage: "✓ Received expected response",
}
if err := testCase.Run(asserter, shell, logger); err != nil {
if err := bareExpansionCall.Run(asserter, shell, logger); err != nil {
return err
}

Expand Down
23 changes: 13 additions & 10 deletions internal/stage_pex6.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ func testPEX6(stageHarness *test_case_harness.TestCaseHarness) error {
return err
}

// Declare two variables with valid names and random values
words := random.RandomWords(6)
words := random.RandomWords(7)
integerSuffixes := random.RandomInts(1, 10, 2)
variableName1 := fmt.Sprintf(
"%s_%d",
Expand All @@ -50,22 +49,26 @@ func testPEX6(stageHarness *test_case_harness.TestCaseHarness) error {
variableValue2 := words[3]
literalPrefix := words[4]
literalSuffix := words[5]
literalSuffix2 := words[6]

if err := (test_cases.DeclareAssignmentTestCase{Variable: variableName1, Value: variableValue1}).Run(asserter, shell, logger); err != nil {
assignVar1 := test_cases.DeclareAssignmentTestCase{Variable: variableName1, Value: variableValue1}
if err := assignVar1.Run(asserter, shell, logger); err != nil {
return err
}
if err := (test_cases.DeclareAssignmentTestCase{Variable: variableName2, Value: variableValue2}).Run(asserter, shell, logger); err != nil {
assignVar2 := test_cases.DeclareAssignmentTestCase{Variable: variableName2, Value: variableValue2}
if err := assignVar2.Run(asserter, shell, logger); err != nil {
return err
}

// Run the executable with ${VAR1} embedded between random literal prefix/suffix,
// and ${VAR2} as a standalone brace-expansion argument.
// Run the executable with:
// prefix_${VAR1}_suffix — braces in the middle of a word
// ${VAR2}_literal — braces at the start of a word
argument1 := fmt.Sprintf("%s_${%s}_%s", literalPrefix, variableName1, literalSuffix)
argument2 := fmt.Sprintf("${%s}", variableName2)
argument2 := fmt.Sprintf("${%s}_%s", variableName2, literalSuffix2)
command := fmt.Sprintf("%s %s %s", executableName, argument1, argument2)

expectedArgument1 := fmt.Sprintf("%s_%s_%s", literalPrefix, variableValue1, literalSuffix)
expectedArgument2 := variableValue2
expectedArgument2 := fmt.Sprintf("%s_%s", variableValue2, literalSuffix2)

expectedLines := []string{
"Program was passed 3 args (including program name).",
Expand All @@ -75,12 +78,12 @@ func testPEX6(stageHarness *test_case_harness.TestCaseHarness) error {
fmt.Sprintf("Program Signature: %s", commandMetadata),
}

testCase := test_cases.CommandWithMultilineResponseTestCase{
bracedExpansionCall := test_cases.CommandWithMultilineResponseTestCase{
Command: command,
MultiLineAssertion: assertions.NewMultiLineAssertion(expectedLines),
SuccessMessage: "✓ Received expected response",
}
if err := testCase.Run(asserter, shell, logger); err != nil {
if err := bracedExpansionCall.Run(asserter, shell, logger); err != nil {
return err
}

Expand Down
22 changes: 15 additions & 7 deletions internal/stage_pex7.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,41 @@ func testPEX7(stageHarness *test_case_harness.TestCaseHarness) error {
return err
}

existingValue := random.RandomWords(1)[0]
if err := (test_cases.DeclareAssignmentTestCase{Variable: "existing", Value: existingValue}).Run(asserter, shell, logger); err != nil {
words := random.RandomWords(2)
variableName := words[0]
variableValue := words[1]

assignVariable := test_cases.DeclareAssignmentTestCase{Variable: variableName, Value: variableValue}
if err := assignVariable.Run(asserter, shell, logger); err != nil {
return err
}

integerSuffixes := random.RandomInts(1, 99, 2)
integerSuffixes := random.RandomInts(0, 10, 3)

command := fmt.Sprintf(
"%s ${missing_var_%d}_suffix ${existing} ${missing_var_%d}",
"%s ${missing_var_%d}_suffix ${%s} ${missing_var_%d} $missing_var_%d",
executableName,
integerSuffixes[0],
variableName,
integerSuffixes[1],
integerSuffixes[2],
)

expectedLines := []string{
"Program was passed 3 args (including program name).",
fmt.Sprintf("Arg #0 (program name): %s", executableName),
"Arg #1: _suffix",
fmt.Sprintf("Arg #2: %s", existingValue),
fmt.Sprintf("Arg #2: %s", variableValue),
fmt.Sprintf("Program Signature: %s", commandMetadata),
}

testCase := test_cases.CommandWithMultilineResponseTestCase{
missingVarExpansionCall := test_cases.CommandWithMultilineResponseTestCase{
Command: command,
MultiLineAssertion: assertions.NewMultiLineAssertion(expectedLines),
SuccessMessage: "✓ Received expected response",
}
if err := testCase.Run(asserter, shell, logger); err != nil {

if err := missingVarExpansionCall.Run(asserter, shell, logger); err != nil {
return err
}

Expand Down
6 changes: 5 additions & 1 deletion internal/test_cases/declare_assignment_test_case.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ func (t DeclareAssignmentTestCase) Run(asserter *logged_shell_asserter.LoggedShe
}

expectedOutput := fmt.Sprintf("declare: `%s': not a valid identifier", assignment)

testCase := CommandResponseTestCase{
Command: fmt.Sprintf("declare %s", assignment),
ExpectedOutput: expectedOutput,
FallbackPatterns: []*regexp.Regexp{
// bash prefixes with "bash: "
regexp.MustCompile(fmt.Sprintf("^bash: declare: `%s': not a valid identifier$", regexp.QuoteMeta(assignment))),
// zsh uses "not an identifier" phrasing
// zsh uses "not an identifier" for names starting with a digit
regexp.MustCompile(fmt.Sprintf(`^declare: not an identifier: %s$`, regexp.QuoteMeta(t.Variable))),
// zsh uses "not valid in this context"
// for names with valid starting character but with invalid characters in the middle
regexp.MustCompile(fmt.Sprintf(`^declare: not valid in this context: %s$`, regexp.QuoteMeta(t.Variable))),
},
SuccessMessage: "✓ Received expected response",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@ import (
"github.com/codecrafters-io/tester-utils/logger"
)

// DeclarePrintErrorTestCase tests `declare -p VAR` when the variable does not exist.
// DeclarePrintMissingVariableTestCase tests `declare -p VAR` when the variable does not exist.
// Expected output: declare: VAR: not found
type DeclarePrintErrorTestCase struct {
type DeclarePrintMissingVariableTestCase struct {
Variable string
}

func (t DeclarePrintErrorTestCase) Run(asserter *logged_shell_asserter.LoggedShellAsserter, shell *shell_executable.ShellExecutable, logger *logger.Logger) error {
func (t DeclarePrintMissingVariableTestCase) Run(asserter *logged_shell_asserter.LoggedShellAsserter, shell *shell_executable.ShellExecutable, logger *logger.Logger) error {
fallbackPatterns := []*regexp.Regexp{
regexp.MustCompile(fmt.Sprintf(`^bash: declare: %s: not found$`, regexp.QuoteMeta(t.Variable))),
regexp.MustCompile(fmt.Sprintf(`^declare: no such variable: %s$`, regexp.QuoteMeta(t.Variable))),
}

if !isValidIdentifier(t.Variable) {
// zsh rejects invalid identifiers passed to -p
fallbackPatterns = append(fallbackPatterns, regexp.MustCompile(fmt.Sprintf(`^declare: bad argument to -p: %s$`, regexp.QuoteMeta(t.Variable))))
panic(fmt.Sprintf("Codecrafters Internal Error - DeclarePrintMissingVariableTestCase called on invalid identifier %s", t.Variable))
}

testCase := CommandResponseTestCase{
Expand Down
24 changes: 12 additions & 12 deletions internal/test_helpers/fixtures/bash/parameter_expansion/pass
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ Debug = true
[your-program] $ declare 5raspberry=apple
[your-program] bash: declare: `5raspberry=apple': not a valid identifier
[tester::#DB8] ✓ Received expected response
[your-program] $ declare pear-grape=orange
[your-program] bash: declare: `pear-grape=orange': not a valid identifier
[tester::#DB8] ✓ Received expected response
[your-program] $ declare -p _pineapple
[your-program] declare -- _pineapple="orange"
[tester::#DB8] ✓ Received expected response
[your-program] $ declare -p 5raspberry
[your-program] bash: declare: 5raspberry: not found
[tester::#DB8] ✓ Received expected response
[your-program] $ 
[tester::#DB8] Test passed.

Expand All @@ -55,15 +55,15 @@ Debug = true
[tester::#GE9] [setup] Available executables:
[tester::#GE9] [setup] - custom_exe_6864
[tester::#GE9] Running ./your_shell.sh
[your-program] $ declare Grape_1=strawberry
[your-program] $ declare Grape_2=strawberry
[tester::#GE9] ✓ Received expected response
[your-program] $ declare Pear_2=banana
[your-program] $ declare Pear_3=banana
[tester::#GE9] ✓ Received expected response
[your-program] $ custom_exe_6864 $Grape_1 $Pear_2
[your-program] $ custom_exe_6864 $Grape_2 raspberry_$Pear_3
[your-program] Program was passed 3 args (including program name).
[your-program] Arg #0 (program name): custom_exe_6864
[your-program] Arg #1: strawberry
[your-program] Arg #2: banana
[your-program] Arg #2: raspberry_banana
[your-program] Program Signature: 2314588360
[tester::#GE9] ✓ Received expected response
[your-program] $ 
Expand All @@ -78,11 +78,11 @@ Debug = true
[tester::#BR2] ✓ Received expected response
[your-program] $ declare Pineapple_9=pear
[tester::#BR2] ✓ Received expected response
[your-program] $ custom_exe_5355 grape_${Raspberry_2}_banana ${Pineapple_9}
[your-program] $ custom_exe_5355 grape_${Raspberry_2}_banana ${Pineapple_9}_orange
[your-program] Program was passed 3 args (including program name).
[your-program] Arg #0 (program name): custom_exe_5355
[your-program] Arg #1: grape_mango_banana
[your-program] Arg #2: pear
[your-program] Arg #2: pear_orange
[your-program] Program Signature: 4420233970
[tester::#BR2] ✓ Received expected response
[your-program] $ 
Expand All @@ -93,13 +93,13 @@ Debug = true
[tester::#MY0] [setup] Available executables:
[tester::#MY0] [setup] - custom_exe_8866
[tester::#MY0] Running ./your_shell.sh
[your-program] $ declare existing=mango
[your-program] $ declare mango=orange
[tester::#MY0] ✓ Received expected response
[your-program] $ custom_exe_8866 ${missing_var_8}_suffix ${existing} ${missing_var_46}
[your-program] $ custom_exe_8866 ${missing_var_9}_suffix ${mango} ${missing_var_7} $missing_var_6
[your-program] Program was passed 3 args (including program name).
[your-program] Arg #0 (program name): custom_exe_8866
[your-program] Arg #1: _suffix
[your-program] Arg #2: mango
[your-program] Arg #2: orange
[your-program] Program Signature: 7313273050
[tester::#MY0] ✓ Received expected response
[your-program] $ 
Expand Down
Loading