diff --git a/internal/stage_pex2.go b/internal/stage_pex2.go index fc2e1ef8..782781af 100644 --- a/internal/stage_pex2.go +++ b/internal/stage_pex2.go @@ -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, } diff --git a/internal/stage_pex3.go b/internal/stage_pex3.go index 80887729..d15b62fb 100644 --- a/internal/stage_pex3.go +++ b/internal/stage_pex3.go @@ -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 } diff --git a/internal/stage_pex4.go b/internal/stage_pex4.go index cd8e5aa9..68aec765 100644 --- a/internal/stage_pex4.go +++ b/internal/stage_pex4.go @@ -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 } diff --git a/internal/stage_pex5.go b/internal/stage_pex5.go index 5cc7554d..36a49a21 100644 --- a/internal/stage_pex5.go +++ b/internal/stage_pex5.go @@ -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:], @@ -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 } diff --git a/internal/stage_pex6.go b/internal/stage_pex6.go index 68196926..916209fb 100644 --- a/internal/stage_pex6.go +++ b/internal/stage_pex6.go @@ -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", @@ -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).", @@ -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 } diff --git a/internal/stage_pex7.go b/internal/stage_pex7.go index 4cc4a6ea..59bae526 100644 --- a/internal/stage_pex7.go +++ b/internal/stage_pex7.go @@ -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 } diff --git a/internal/test_cases/declare_assignment_test_case.go b/internal/test_cases/declare_assignment_test_case.go index 91500eb9..0b96bc56 100644 --- a/internal/test_cases/declare_assignment_test_case.go +++ b/internal/test_cases/declare_assignment_test_case.go @@ -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", } diff --git a/internal/test_cases/declare_print_error_test_case.go b/internal/test_cases/declare_print_missing_variable_test_case.go similarity index 65% rename from internal/test_cases/declare_print_error_test_case.go rename to internal/test_cases/declare_print_missing_variable_test_case.go index cdbbd64e..ad09b0c9 100644 --- a/internal/test_cases/declare_print_error_test_case.go +++ b/internal/test_cases/declare_print_missing_variable_test_case.go @@ -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{ diff --git a/internal/test_helpers/fixtures/bash/parameter_expansion/pass b/internal/test_helpers/fixtures/bash/parameter_expansion/pass index 8dd47a3b..635465ac 100644 --- a/internal/test_helpers/fixtures/bash/parameter_expansion/pass +++ b/internal/test_helpers/fixtures/bash/parameter_expansion/pass @@ -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. @@ -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] $  @@ -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] $  @@ -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] $