diff --git a/.github/workflows/regex.yml b/.github/workflows/regex.yml index 3e06179..ed0654d 100644 --- a/.github/workflows/regex.yml +++ b/.github/workflows/regex.yml @@ -1,4 +1,4 @@ -name: Validate Regex Patterns +name: Regular Expressions on: push: @@ -26,6 +26,4 @@ jobs: run: pwsh scripts/validateAllPatterns.ps1 - name: test - run: | - echo "TODO: Implement unit tests for all patterns" - # TODO: Add test runner command here + run: pwsh scripts/testPatterns.ps1 diff --git a/scripts/testPatterns.ps1 b/scripts/testPatterns.ps1 new file mode 100644 index 0000000..3a290b3 --- /dev/null +++ b/scripts/testPatterns.ps1 @@ -0,0 +1,183 @@ +param( + [string]$YamlFilePath +) + +# Function to run tests for a single file +function Test-Pattern { + param([string]$FilePath) + + $fileName = (Get-Item $FilePath).BaseName + + try { + # Read YAML content + $yamlContent = Get-Content -Path $FilePath -Raw + + # Extract pattern + $patternMatch = [regex]::Match($yamlContent, 'pattern:\s*(.+)') + $pattern = $patternMatch.Groups[1].Value.Trim() + + # Create regex object with case-insensitive and multiline options + $regexOptions = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Multiline + $regex = New-Object System.Text.RegularExpressions.Regex($pattern, $regexOptions) + + # Extract tests + $testsSection = [regex]::Match($yamlContent, '(?s)tests:(.*?)(?:\n\w|\z)').Groups[1].Value + + if ([string]::IsNullOrWhiteSpace($testsSection)) { + return @{ + File = $fileName + Passed = 0 + Failed = 0 + Total = 0 + } + } + + # Parse each test + $tests = [regex]::Matches($testsSection, '(?s)- expected:\s*(true|false).*?input:\s*(.+?)(?=\n\s*\w|\n-|\z)') + + $passed = 0 + $failed = 0 + $failureDetails = @() + + foreach ($test in $tests) { + $expected = $test.Groups[1].Value -eq 'true' + $input = $test.Groups[2].Value.Trim() + + # Run the test + $matches = $regex.IsMatch($input) + + if ($matches -eq $expected) { + $passed++ + } + else { + $failed++ + $failureDetails += " Input: '$input' - Expected: $expected, Got: $matches" + } + } + + return @{ + File = $fileName + Passed = $passed + Failed = $failed + Total = $tests.Count + Failures = $failureDetails + } + } + catch { + return @{ + File = $fileName + Error = $_.Exception.Message + } + } +} + +# Main execution +if ($YamlFilePath) { + # Test single file + $result = Test-Pattern -FilePath $YamlFilePath + + if ($result.Error) { + Write-Host "ERROR: $($result.File): $($result.Error)" -ForegroundColor Red + exit 1 + } + + if ($result.Failed -gt 0) { + Write-Host "$($result.File): $($result.Failed)/$($result.Total) tests failed" -ForegroundColor Red + foreach ($failure in $result.Failures) { + Write-Host $failure + } + exit 1 + } + else { + Write-Host "$($result.File): All $($result.Total) tests passed" + exit 0 + } +} +else { + # Test all files + $patternFiles = @() + $patternFiles += Get-ChildItem -Path "regex_patterns" -Filter "*.yml" -File + $patternFiles += Get-ChildItem -Path "regex_patterns" -Filter "*.yaml" -File + + if ($patternFiles.Count -eq 0) { + Write-Host "No pattern files found" + exit 1 + } + + $results = @() + $totalFiles = $patternFiles.Count + $filesWithFailures = @() + + foreach ($file in $patternFiles) { + $result = Test-Pattern -FilePath $file.FullName + $results += $result + + if ($result.Error) { + $filesWithFailures += $result + } + elseif ($result.Failed -gt 0) { + $filesWithFailures += $result + } + } + + # Report failures + if ($filesWithFailures.Count -gt 0) { + Write-Host "ERRORS:" + + # Find global max input length across all failures + $globalMaxInputLength = 0 + foreach ($failure in $filesWithFailures) { + if (-not $failure.Error) { + foreach ($detail in $failure.Failures) { + if ($detail -match "Input: '(.+?)' -") { + $inputLength = $matches[1].Length + if ($inputLength -gt $globalMaxInputLength) { + $globalMaxInputLength = $inputLength + } + } + } + } + } + + # Limit padding to 150 characters + if ($globalMaxInputLength -gt 150) { + $globalMaxInputLength = 150 + } + + foreach ($failure in $filesWithFailures) { + Write-Host " $($failure.File):" + + if ($failure.Error) { + # Extract cleaner error message + $errorMsg = $failure.Error + if ($errorMsg -match "at offset (\d+)\. (.+?)\.?`"") { + $errorMsg = "offset $($matches[1]): $($matches[2])" + } + Write-Host " $errorMsg" + } + else { + foreach ($detail in $failure.Failures) { + if ($detail -match "Input: '(.+?)' - Expected: (\w+), Got: (\w+)") { + $input = $matches[1] + $expected = $matches[2] + $got = $matches[3] + + # Truncate only if longer than 150 + if ($input.Length -gt 150) { + $input = $input.Substring(0, 147) + "..." + } + + $paddedInput = $input.PadRight($globalMaxInputLength) + Write-Host " $paddedInput | Expected: $expected | Got: $got" + } + } + } + } + + exit 1 + } + else { + Write-Host "All tests passed" + exit 0 + } +} \ No newline at end of file diff --git a/scripts/unitTest.ps1 b/scripts/unitTest.ps1 deleted file mode 100644 index 11c112d..0000000 --- a/scripts/unitTest.ps1 +++ /dev/null @@ -1 +0,0 @@ -# TODO: Run a single unit test given a regular expression and unit test. A unit test includes the input string and whether it should match or not. \ No newline at end of file diff --git a/scripts/validatePattern.ps1 b/scripts/validatePattern.ps1 deleted file mode 100644 index f4d1d5a..0000000 --- a/scripts/validatePattern.ps1 +++ /dev/null @@ -1,73 +0,0 @@ -param( - [Parameter(Mandatory=$true)] - [string]$YamlFilePath -) - -$moduleName = "regex" - -function Write-Log { - param( - [Parameter(Mandatory=$true)] - [string]$Level, - [Parameter(Mandatory=$true)] - [string]$Message - ) - - $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" - $levelPadded = $Level.ToUpper().PadRight(7) - $modulePadded = $moduleName.PadRight(0) - - # Write colored level - Write-Host -NoNewline "[" - if ($Level -eq "ERROR") { - Write-Host -NoNewline $levelPadded -ForegroundColor Red - } - elseif ($Level -eq "SUCCESS") { - Write-Host -NoNewline $levelPadded -ForegroundColor Green - } - elseif ($Level -eq "INFO") { - Write-Host -NoNewline $levelPadded -ForegroundColor Cyan - } - else { - Write-Host -NoNewline $levelPadded - } - Write-Host -NoNewline "] " - - # Write grey timestamp - Write-Host -NoNewline "[$timestamp] " -ForegroundColor DarkGray - - # Write module and message in normal color - Write-Host "[$modulePadded] $Message" -} - -try { - # Check if file exists - if (-not (Test-Path $YamlFilePath)) { - Write-Log -Level "ERROR" -Message "YAML file not found: $YamlFilePath" - exit 1 - } - - # Read YAML content - $yamlContent = Get-Content -Path $YamlFilePath -Raw - - # Extract pattern field from YAML - $patternMatch = [regex]::Match($yamlContent, 'pattern:\s*(.+)') - $pattern = $patternMatch.Groups[1].Value.Trim() - - Write-Log -Level "INFO" -Message "Found pattern: $pattern" - - # Validate the pattern against .NET regex engine - try { - $regex = New-Object System.Text.RegularExpressions.Regex($pattern) - Write-Log -Level "SUCCESS" -Message "Valid regex pattern" - exit 0 - } - catch { - Write-Log -Level "ERROR" -Message "Pattern validation failed: $_" - exit 1 - } -} -catch { - Write-Log -Level "ERROR" -Message "Script execution failed: $_" - exit 1 -} \ No newline at end of file diff --git a/scripts/validateAllPatterns.ps1 b/scripts/validatePatterns.ps1 similarity index 100% rename from scripts/validateAllPatterns.ps1 rename to scripts/validatePatterns.ps1