From 788adeeed1a0a41fb33366a50a3bc185aacc363f Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Fri, 27 Dec 2024 21:24:46 +0530 Subject: [PATCH 1/6] refactor: update logger success message formatting --- internal/test_cases/command_reflection_test_case.go | 2 +- internal/test_cases/command_response_test_case.go | 2 +- .../test_cases/command_with_multiline_response_test_case.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/test_cases/command_reflection_test_case.go b/internal/test_cases/command_reflection_test_case.go index 84652994..1cb5ff9d 100644 --- a/internal/test_cases/command_reflection_test_case.go +++ b/internal/test_cases/command_reflection_test_case.go @@ -46,7 +46,7 @@ func (t CommandReflectionTestCase) Run(asserter *logged_shell_asserter.LoggedShe } if !skipSuccessMessage { - logger.Successf(t.SuccessMessage) + logger.Successf("%s", t.SuccessMessage) } return nil } diff --git a/internal/test_cases/command_response_test_case.go b/internal/test_cases/command_response_test_case.go index 1b0e06b1..667a33a8 100644 --- a/internal/test_cases/command_response_test_case.go +++ b/internal/test_cases/command_response_test_case.go @@ -48,6 +48,6 @@ func (t CommandResponseTestCase) Run(asserter *logged_shell_asserter.LoggedShell return err } - logger.Successf(t.SuccessMessage) + logger.Successf("%s", t.SuccessMessage) return nil } diff --git a/internal/test_cases/command_with_multiline_response_test_case.go b/internal/test_cases/command_with_multiline_response_test_case.go index d4a12aaf..43158304 100644 --- a/internal/test_cases/command_with_multiline_response_test_case.go +++ b/internal/test_cases/command_with_multiline_response_test_case.go @@ -43,6 +43,6 @@ func (t CommandWithMultilineResponseTestCase) Run(asserter *logged_shell_asserte return err } - logger.Successf(t.SuccessMessage) + logger.Successf("%s", t.SuccessMessage) return nil } From b0145ed7cc3a1872ee5aa6380e52a421a42aa68b Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Fri, 27 Dec 2024 21:28:07 +0530 Subject: [PATCH 2/6] refactor: enhance MultiLineAssertion structure and methods - Introduced SingleLineAssertions to allow for more granular assertions. - Added NewMultiLineAssertion and NewEmptyMultiLineAssertion constructors. - Implemented AddSingleLineAssertion method for adding single line assertions with fallback patterns. - Updated Inspect and Run methods to accommodate the new structure. --- internal/assertions/multi_line_assertion.go | 75 +++++++++++---------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/internal/assertions/multi_line_assertion.go b/internal/assertions/multi_line_assertion.go index f5a979b7..fc4689c9 100644 --- a/internal/assertions/multi_line_assertion.go +++ b/internal/assertions/multi_line_assertion.go @@ -3,54 +3,59 @@ package assertions import ( "fmt" "regexp" - "strings" - - "github.com/codecrafters-io/shell-tester/internal/utils" - virtual_terminal "github.com/codecrafters-io/shell-tester/internal/vt" ) // MultiLineAssertion asserts that multiple lines of output matches against a given array of strings // Or a multi-line regex pattern(s) type MultiLineAssertion struct { - // ExpectedOutput is the array of expected output strings to match against - ExpectedOutput []string - - // FallbackPatterns is a list of regex patterns to match against. This is useful to handle shell-specific variable behaviour - FallbackPatterns []*regexp.Regexp -} - -func (a MultiLineAssertion) Inspect() string { - return fmt.Sprintf("MultiLineAssertion (%q)", a.ExpectedOutput) + SingleLineAssertions []SingleLineAssertion } -func (a MultiLineAssertion) Run(screenState [][]string, startRowIndex int) (processedRowCount int, err *AssertionError) { - if len(a.ExpectedOutput) == 0 { - panic("CodeCrafters Internal Error: ExpectedOutput must be provided") +func NewMultiLineAssertion(expectedOutput []string) MultiLineAssertion { + // No way to add fallbackPatterns through this constructor + singleLineAssertions := []SingleLineAssertion{} + for _, expectedLine := range expectedOutput { + singleLineAssertions = append(singleLineAssertions, SingleLineAssertion{ + ExpectedOutput: expectedLine, + }) } - totalRows := len(a.ExpectedOutput) - rawRows := screenState[startRowIndex : startRowIndex+totalRows] - cleanedRows := []string{} - for _, rawRow := range rawRows { - cleanedRows = append(cleanedRows, virtual_terminal.BuildCleanedRow(rawRow)) + fmt.Println(singleLineAssertions) + + return MultiLineAssertion{ + SingleLineAssertions: singleLineAssertions, } - cleanedRowsString := strings.Join(cleanedRows, "\n") - expectedOutputString := strings.Join(a.ExpectedOutput, "\n") +} - for _, pattern := range a.FallbackPatterns { - if pattern.Match([]byte(cleanedRowsString)) { - return len(a.ExpectedOutput), nil - } +func NewEmptyMultiLineAssertion() MultiLineAssertion { + return MultiLineAssertion{ + SingleLineAssertions: []SingleLineAssertion{}, } +} + +// AddSingleLineAssertion is the recommended way to add single line assertions +// When they contain fallbackPatterns +func (a *MultiLineAssertion) AddSingleLineAssertion(expectedOutput string, fallbackPatterns []*regexp.Regexp) *MultiLineAssertion { + a.SingleLineAssertions = append(a.SingleLineAssertions, SingleLineAssertion{ + ExpectedOutput: expectedOutput, + FallbackPatterns: fallbackPatterns, + }) + return a +} + +func (a *MultiLineAssertion) Inspect() string { + return fmt.Sprintf("MultiLineAssertion (%q)", a.SingleLineAssertions) +} + +func (a *MultiLineAssertion) Run(screenState [][]string, startRowIndex int) (processedRowCount int, err *AssertionError) { + totalProcessedRowCount := 0 - if cleanedRowsString != expectedOutputString { - detailedErrorMessage := utils.BuildColoredErrorMessage(expectedOutputString, cleanedRowsString) - return 0, &AssertionError{ - StartRowIndex: startRowIndex, - ErrorRowIndex: startRowIndex, - Message: "Output does not match expected value.\n" + detailedErrorMessage, + for _, singleLineAssertion := range a.SingleLineAssertions { + processedRowCount, err = singleLineAssertion.Run(screenState, startRowIndex+totalProcessedRowCount) + if err != nil { + return totalProcessedRowCount, err } - } else { - return len(a.ExpectedOutput), nil + totalProcessedRowCount += processedRowCount } + return totalProcessedRowCount, nil } From 0a202186ed77b529b11dc8cdf8587b9a720f3503 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Fri, 27 Dec 2024 21:28:20 +0530 Subject: [PATCH 3/6] refactor: simplify CommandWithMultilineResponseTestCase structure - Removed ExpectedOutput and FallbackPatterns fields. - Introduced MultiLineAssertion field to streamline assertion handling. - Updated Run method to utilize the new MultiLineAssertion structure. --- .../command_with_multiline_response_test_case.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/internal/test_cases/command_with_multiline_response_test_case.go b/internal/test_cases/command_with_multiline_response_test_case.go index 43158304..b03ba1d7 100644 --- a/internal/test_cases/command_with_multiline_response_test_case.go +++ b/internal/test_cases/command_with_multiline_response_test_case.go @@ -2,7 +2,6 @@ package test_cases import ( "fmt" - "regexp" "github.com/codecrafters-io/shell-tester/internal/assertions" "github.com/codecrafters-io/shell-tester/internal/logged_shell_asserter" @@ -14,11 +13,8 @@ type CommandWithMultilineResponseTestCase struct { // Command is the command to send to the shell Command string - // ExpectedOutput is the expected output string to match against - ExpectedOutput []string - - // FallbackPatterns is a list of regex patterns to match against - FallbackPatterns []*regexp.Regexp + // MultiLineAssertion is the assertion to run + MultiLineAssertion assertions.MultiLineAssertion // SuccessMessage is the message to log in case of success SuccessMessage string @@ -34,10 +30,7 @@ func (t CommandWithMultilineResponseTestCase) Run(asserter *logged_shell_asserte ExpectedOutput: commandReflection, }) - asserter.AddAssertion(assertions.MultiLineAssertion{ - ExpectedOutput: t.ExpectedOutput, - FallbackPatterns: t.FallbackPatterns, - }) + asserter.AddAssertion(&t.MultiLineAssertion) if err := asserter.AssertWithPrompt(); err != nil { return err From 3ec77e8446c5026320d1cbc02ae4d68e2c91c362 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Fri, 27 Dec 2024 21:28:40 +0530 Subject: [PATCH 4/6] refactor: update test cases to use MultiLineAssertion - Replaced ExpectedOutput and FallbackPatterns with MultiLineAssertion in CommandWithMultilineResponseTestCase for stages R1 and R3. - Enhanced test case structure for improved assertion handling and consistency across tests. --- internal/stage_r1.go | 10 ++++++---- internal/stage_r3.go | 22 ++++++++++------------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/stage_r1.go b/internal/stage_r1.go index 2f3028da..9b4ece5a 100644 --- a/internal/stage_r1.go +++ b/internal/stage_r1.go @@ -7,6 +7,7 @@ import ( "regexp" "slices" + "github.com/codecrafters-io/shell-tester/internal/assertions" "github.com/codecrafters-io/shell-tester/internal/logged_shell_asserter" "github.com/codecrafters-io/shell-tester/internal/shell_executable" "github.com/codecrafters-io/shell-tester/internal/test_cases" @@ -65,11 +66,12 @@ func testR1(stageHarness *test_case_harness.TestCaseHarness) error { return err } + fmt.Println(randomWords) + multiLineTestCase := test_cases.CommandWithMultilineResponseTestCase{ - Command: command2, - ExpectedOutput: randomWords, - FallbackPatterns: nil, - SuccessMessage: "✓ Received redirected file content", + Command: command2, + MultiLineAssertion: assertions.NewMultiLineAssertion(randomWords), + SuccessMessage: "✓ Received redirected file content", } if err := multiLineTestCase.Run(asserter, shell, logger); err != nil { return err diff --git a/internal/stage_r3.go b/internal/stage_r3.go index f8599b93..8f4356ca 100644 --- a/internal/stage_r3.go +++ b/internal/stage_r3.go @@ -5,6 +5,7 @@ import ( "path" "slices" + "github.com/codecrafters-io/shell-tester/internal/assertions" "github.com/codecrafters-io/shell-tester/internal/logged_shell_asserter" "github.com/codecrafters-io/shell-tester/internal/shell_executable" "github.com/codecrafters-io/shell-tester/internal/test_cases" @@ -64,10 +65,9 @@ func testR3(stageHarness *test_case_harness.TestCaseHarness) error { } responseTestCase := test_cases.CommandWithMultilineResponseTestCase{ - Command: command2, - ExpectedOutput: randomWords, - FallbackPatterns: nil, - SuccessMessage: "✓ Received redirected file content", + Command: command2, + MultiLineAssertion: assertions.NewMultiLineAssertion(randomWords), + SuccessMessage: "✓ Received redirected file content", } if err := responseTestCase.Run(asserter, shell, logger); err != nil { @@ -98,10 +98,9 @@ func testR3(stageHarness *test_case_harness.TestCaseHarness) error { } responseTestCase = test_cases.CommandWithMultilineResponseTestCase{ - Command: command6, - ExpectedOutput: []string{message1, message2}, - FallbackPatterns: nil, - SuccessMessage: "✓ Received redirected file content", + Command: command6, + MultiLineAssertion: assertions.NewMultiLineAssertion([]string{message1, message2}), + SuccessMessage: "✓ Received redirected file content", } if err := responseTestCase.Run(asserter, shell, logger); err != nil { return err @@ -129,10 +128,9 @@ func testR3(stageHarness *test_case_harness.TestCaseHarness) error { } responseTestCase = test_cases.CommandWithMultilineResponseTestCase{ - Command: command9, - ExpectedOutput: append([]string{"List of files:"}, randomWords...), - FallbackPatterns: nil, - SuccessMessage: "✓ Received redirected file content", + Command: command9, + MultiLineAssertion: assertions.NewMultiLineAssertion(append([]string{"List of files:"}, randomWords...)), + SuccessMessage: "✓ Received redirected file content", } if err := responseTestCase.Run(asserter, shell, logger); err != nil { return err From 11b3d9887cfa55c52f55c47749fb3ba27d3f5610 Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Fri, 27 Dec 2024 21:28:55 +0530 Subject: [PATCH 5/6] refactor: enhance test case for R4 with MultiLineAssertion - Updated testR4 function to utilize MultiLineAssertion for error message validation. - Replaced ExpectedOutput and FallbackPatterns with a more structured approach using AddSingleLineAssertion. - Improved regex handling for error messages to ensure accurate assertions. --- internal/stage_r4.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/stage_r4.go b/internal/stage_r4.go index 59c16342..944a2a2f 100644 --- a/internal/stage_r4.go +++ b/internal/stage_r4.go @@ -131,17 +131,20 @@ func testR4(stageHarness *test_case_harness.TestCaseHarness) error { "cat: nonexistent: No such file or directory", "ls: nonexistent: No such file or directory", } + linuxLSErrorMessage := "ls: cannot access 'nonexistent': No such file or directory" + linuxLSErrorMessageRegex := []*regexp.Regexp{regexp.MustCompile(fmt.Sprintf("^%s$", linuxLSErrorMessage))} alpineCatErrorMessage := "cat: can't open 'nonexistent': No such file or directory" - essorMessagesInFileRegex := []*regexp.Regexp{ - regexp.MustCompile(fmt.Sprintf("^%s\n%s$", errorMessagesInFile[0], linuxLSErrorMessage)), - regexp.MustCompile(fmt.Sprintf("^%s\n%s$", alpineCatErrorMessage, errorMessagesInFile[1])), - } + alpineCatErrorMessageRegex := []*regexp.Regexp{regexp.MustCompile(fmt.Sprintf("^%s$", alpineCatErrorMessage))} + + multiLineAssertion := assertions.NewEmptyMultiLineAssertion() + multiLineAssertion.AddSingleLineAssertion(errorMessagesInFile[0], alpineCatErrorMessageRegex) + multiLineAssertion.AddSingleLineAssertion(errorMessagesInFile[1], linuxLSErrorMessageRegex) + multiLineResponseTestCase := test_cases.CommandWithMultilineResponseTestCase{ - Command: command7, - ExpectedOutput: errorMessagesInFile, - FallbackPatterns: essorMessagesInFileRegex, - SuccessMessage: "✓ Received redirected file content", + Command: command7, + MultiLineAssertion: multiLineAssertion, + SuccessMessage: "✓ Received redirected file content", } if err := multiLineResponseTestCase.Run(asserter, shell, logger); err != nil { return err From 82d30580662a5c881c45d96ba42adc624721a30b Mon Sep 17 00:00:00 2001 From: Ryan Gang Date: Fri, 27 Dec 2024 21:32:40 +0530 Subject: [PATCH 6/6] chore: remove extra logs --- internal/assertions/multi_line_assertion.go | 2 -- internal/stage_r1.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/internal/assertions/multi_line_assertion.go b/internal/assertions/multi_line_assertion.go index fc4689c9..654e4185 100644 --- a/internal/assertions/multi_line_assertion.go +++ b/internal/assertions/multi_line_assertion.go @@ -20,8 +20,6 @@ func NewMultiLineAssertion(expectedOutput []string) MultiLineAssertion { }) } - fmt.Println(singleLineAssertions) - return MultiLineAssertion{ SingleLineAssertions: singleLineAssertions, } diff --git a/internal/stage_r1.go b/internal/stage_r1.go index 9b4ece5a..ad385c2f 100644 --- a/internal/stage_r1.go +++ b/internal/stage_r1.go @@ -66,8 +66,6 @@ func testR1(stageHarness *test_case_harness.TestCaseHarness) error { return err } - fmt.Println(randomWords) - multiLineTestCase := test_cases.CommandWithMultilineResponseTestCase{ Command: command2, MultiLineAssertion: assertions.NewMultiLineAssertion(randomWords),