|
| 1 | +#!/usr/bin/env perl |
| 2 | + |
| 3 | +use strict; |
| 4 | +use warnings; |
| 5 | +use Test::More; |
| 6 | + |
| 7 | +BEGIN { |
| 8 | + use_ok('Data::Random::String::Matches'); |
| 9 | +} |
| 10 | + |
| 11 | +# =========================================================================== |
| 12 | +# Regression test for quote handling in character classes |
| 13 | +# Issue: Argument "#" isn't numeric in range (or flop) |
| 14 | +# Pattern: qr/^[!#-'*+\\-\\.\\^_`|~0-9A-Za-z]+$/ |
| 15 | +# =========================================================================== |
| 16 | + |
| 17 | +subtest 'Character class with single quotes in range' => sub { |
| 18 | + # The problematic pattern that caused the original error |
| 19 | + my $gen = Data::Random::String::Matches->new(qr/^[!#-'*+\\-\\.\\^_`|~0-9A-Za-z]+$/); |
| 20 | + |
| 21 | + # Should not die |
| 22 | + my $str; |
| 23 | + eval { |
| 24 | + $str = $gen->generate(); |
| 25 | + }; |
| 26 | + |
| 27 | + ok(!$@, 'Generates without error') or diag("Error: $@"); |
| 28 | + ok(defined $str, 'Generated string is defined'); |
| 29 | + |
| 30 | + if (defined $str) { |
| 31 | + # Verify it matches the pattern |
| 32 | + like($str, qr/^[!#-'*+\\-\\.\\^_`|~0-9A-Za-z]+$/, 'Generated string matches pattern'); |
| 33 | + |
| 34 | + # Check that string contains valid characters |
| 35 | + my @chars = split //, $str; |
| 36 | + for my $char (@chars) { |
| 37 | + my $ord = ord($char); |
| 38 | + # Valid ranges: ! (33), #-' (35-39), * (42), + (43), - (45), . (46), |
| 39 | + # ^ (94), _ (95), ` (96), | (124), ~ (126), 0-9 (48-57), A-Z (65-90), a-z (97-122) |
| 40 | + ok( |
| 41 | + $char =~ /[!#-'*+\-\.\^_`|~0-9A-Za-z]/, |
| 42 | + "Character '$char' (ord $ord) is valid" |
| 43 | + ); |
| 44 | + } |
| 45 | + } |
| 46 | +}; |
| 47 | + |
| 48 | +subtest 'Character class with range starting after quote' => sub { |
| 49 | + # Range #-' includes: # $ % & ' |
| 50 | + my $gen = Data::Random::String::Matches->new(qr/[#-']/); |
| 51 | + |
| 52 | + my $str = $gen->generate(); |
| 53 | + ok(defined $str, 'Generated string defined'); |
| 54 | + like($str, qr/^[#-']$/, 'Matches range #-\''); |
| 55 | + |
| 56 | + # Verify it's one of the expected characters |
| 57 | + ok($str =~ /^[#\$%&']$/, "Character is in range: $str"); |
| 58 | +}; |
| 59 | + |
| 60 | +subtest 'Character class with quote as range end' => sub { |
| 61 | + # Test various ranges ending in quotes |
| 62 | + my @test_patterns = ( |
| 63 | + qr/[!-']/, # ! " # $ % & ' |
| 64 | + qr/["-']/, # " # $ % & ' |
| 65 | + qr/[#-']/, # # $ % & ' |
| 66 | + ); |
| 67 | + |
| 68 | + for my $pattern (@test_patterns) { |
| 69 | + my $gen = Data::Random::String::Matches->new($pattern); |
| 70 | + my $str = $gen->generate(); |
| 71 | + |
| 72 | + ok(defined $str, "Pattern $pattern generates successfully"); |
| 73 | + like($str, $pattern, 'Generated string matches pattern'); |
| 74 | + } |
| 75 | +}; |
| 76 | + |
| 77 | +subtest 'Character class with double quotes in range' => sub { |
| 78 | + # Range with double quotes |
| 79 | + my $gen = Data::Random::String::Matches->new(qr/[!-"]/); |
| 80 | + |
| 81 | + my $str = $gen->generate(); |
| 82 | + ok(defined $str, 'Generated with double quote range'); |
| 83 | + like($str, qr/^[!-"]$/, 'Matches range with double quote'); |
| 84 | + |
| 85 | + # Should be ! or " |
| 86 | + ok($str eq '!' || $str eq '"', "Character is ! or \": got '$str'"); |
| 87 | +}; |
| 88 | + |
| 89 | +subtest 'Character class with backtick' => sub { |
| 90 | + # Backtick in character class |
| 91 | + my $gen = Data::Random::String::Matches->new(qr/[_`a]/); |
| 92 | + |
| 93 | + my $str = $gen->generate(); |
| 94 | + ok(defined $str, 'Generated with backtick'); |
| 95 | + like($str, qr/^[_`a]$/, 'Matches pattern with backtick'); |
| 96 | + |
| 97 | + ok($str =~ /^[_`a]$/, "Character is valid: $str"); |
| 98 | +}; |
| 99 | + |
| 100 | +subtest 'Character class with escaped special chars' => sub { |
| 101 | + # Pattern with escaped special characters |
| 102 | + my $gen = Data::Random::String::Matches->new(qr/[a\-z]/); |
| 103 | + |
| 104 | + my $str = $gen->generate(); |
| 105 | + ok(defined $str, 'Generated with escaped dash'); |
| 106 | + |
| 107 | + # Should be 'a', '-', or 'z' (not a range because dash is escaped) |
| 108 | + ok($str =~ /^[az\-]$/, "Character is a, z, or dash: $str"); |
| 109 | +}; |
| 110 | + |
| 111 | +subtest 'Character class with multiple quote types' => sub { |
| 112 | + # Mix of single and double quotes |
| 113 | + my $gen = Data::Random::String::Matches->new(qr/["'`]/); |
| 114 | + |
| 115 | + my $str = $gen->generate(); |
| 116 | + ok(defined $str, 'Generated with multiple quote types'); |
| 117 | + ok($str eq '"' || $str eq "'" || $str eq '`', |
| 118 | + "Character is a quote type: $str"); |
| 119 | +}; |
| 120 | + |
| 121 | +subtest 'Complex character class from original error' => sub { |
| 122 | + # Full pattern that caused the error |
| 123 | + my $pattern = qr/^[!#-'*+\\-\\.\\^_`|~0-9A-Za-z]+$/; |
| 124 | + my $gen = Data::Random::String::Matches->new($pattern); |
| 125 | + |
| 126 | + # Generate multiple times to ensure consistency |
| 127 | + for my $i (1..10) { |
| 128 | + my $str = $gen->generate(); |
| 129 | + ok(defined $str, "Iteration $i: Generated successfully"); |
| 130 | + like($str, $pattern, "Iteration $i: Matches pattern"); |
| 131 | + ok(length($str) > 0, "Iteration $i: Non-empty string"); |
| 132 | + } |
| 133 | +}; |
| 134 | + |
| 135 | +subtest 'Character class range boundaries with quotes' => sub { |
| 136 | + # Test ranges that include quote characters at boundaries |
| 137 | + my @test_cases = ( |
| 138 | + { |
| 139 | + pattern => qr/[!-']/, |
| 140 | + desc => 'Range from ! to \'', |
| 141 | + chars => ['!', '"', '#', '$', '%', '&', "'"], |
| 142 | + }, |
| 143 | + { |
| 144 | + pattern => qr/['-*]/, |
| 145 | + desc => 'Range from \' to *', |
| 146 | + chars => ["'", '(', ')', '*'], |
| 147 | + }, |
| 148 | + ); |
| 149 | + |
| 150 | + for my $test (@test_cases) { |
| 151 | + my $gen = Data::Random::String::Matches->new($test->{pattern}); |
| 152 | + |
| 153 | + # Generate multiple strings |
| 154 | + my %seen; |
| 155 | + for (1..50) { |
| 156 | + my $str = $gen->generate(); |
| 157 | + $seen{$str}++; |
| 158 | + } |
| 159 | + |
| 160 | + # Check we only got valid characters |
| 161 | + for my $char (keys %seen) { |
| 162 | + ok( |
| 163 | + (grep { $_ eq $char } @{$test->{chars}}), |
| 164 | + "$test->{desc}: Character '$char' is valid" |
| 165 | + ); |
| 166 | + } |
| 167 | + |
| 168 | + # Should have some variety (at least 2 different chars in 50 tries) |
| 169 | + cmp_ok(scalar keys %seen, '>=', 2, |
| 170 | + "$test->{desc}: Generated variety"); |
| 171 | + } |
| 172 | +}; |
| 173 | + |
| 174 | +subtest 'Escaped vs unescaped dash in character class' => sub { |
| 175 | + # Escaped dash: literal dash character |
| 176 | + my $gen1 = Data::Random::String::Matches->new(qr/[a\-z]/); |
| 177 | + my %chars1; |
| 178 | + $chars1{$gen1->generate()}++ for (1..30); |
| 179 | + |
| 180 | + # Should only see a, -, z (not b, c, d, etc.) |
| 181 | + for my $char (keys %chars1) { |
| 182 | + ok($char =~ /^[az\-]$/, "Escaped dash pattern: got '$char'"); |
| 183 | + } |
| 184 | + |
| 185 | + # Unescaped dash: range |
| 186 | + my $gen2 = Data::Random::String::Matches->new(qr/[a-z]/); |
| 187 | + my %chars2; |
| 188 | + $chars2{$gen2->generate()}++ for (1..30); |
| 189 | + |
| 190 | + # Should see variety of lowercase letters |
| 191 | + ok(scalar(keys %chars2) >= 5, |
| 192 | + 'Unescaped dash creates range with variety'); |
| 193 | +}; |
| 194 | + |
| 195 | +subtest 'Validate pattern_info with quotes' => sub { |
| 196 | + # Ensure pattern_info doesn't crash on these patterns |
| 197 | + my $gen = Data::Random::String::Matches->new(qr/^[!#-'*+\\-\\.\\^_`|~0-9A-Za-z]+$/); |
| 198 | + |
| 199 | + my $info; |
| 200 | + eval { |
| 201 | + $info = $gen->pattern_info(); |
| 202 | + }; |
| 203 | + |
| 204 | + ok(!$@, 'pattern_info does not crash') or diag("Error: $@"); |
| 205 | + ok(defined $info, 'pattern_info returns defined value'); |
| 206 | + |
| 207 | + if (defined $info) { |
| 208 | + ok(exists $info->{min_length}, 'Has min_length'); |
| 209 | + ok(exists $info->{max_length}, 'Has max_length'); |
| 210 | + ok(exists $info->{complexity}, 'Has complexity'); |
| 211 | + } |
| 212 | +}; |
| 213 | + |
| 214 | +subtest 'Generate many with quote patterns' => sub { |
| 215 | + my $gen = Data::Random::String::Matches->new(qr/[!#-']/); |
| 216 | + |
| 217 | + # Pattern [!#-'] has 6 possible characters: ! " # $ % & ' |
| 218 | + # So we can only generate at most 6 unique single-char strings |
| 219 | + my @strings = $gen->generate_many(6, 1); |
| 220 | + |
| 221 | + cmp_ok(scalar @strings, '>=', 5, 'Generated at least 5 unique strings') |
| 222 | + or diag('Only got ', scalar(@strings)); |
| 223 | + cmp_ok(scalar @strings, '<=', 6, 'Generated at most 6 unique strings (the maximum possible)'); |
| 224 | + |
| 225 | + # All should match |
| 226 | + for my $str (@strings) { |
| 227 | + like($str, qr/^[!#-']$/, "String '$str' matches pattern"); |
| 228 | + } |
| 229 | + |
| 230 | + # Check uniqueness |
| 231 | + my %seen; |
| 232 | + for my $str (@strings) { |
| 233 | + ok(!$seen{$str}, "String '$str' is unique"); |
| 234 | + $seen{$str}++; |
| 235 | + } |
| 236 | +}; |
| 237 | + |
| 238 | +subtest 'Validate with quote patterns' => sub { |
| 239 | + my $gen = Data::Random::String::Matches->new(qr/[#-']/); |
| 240 | + |
| 241 | + # Test valid characters in range |
| 242 | + ok($gen->validate('#'), 'Validates #'); |
| 243 | + ok($gen->validate('$'), 'Validates $'); |
| 244 | + ok($gen->validate('%'), 'Validates %'); |
| 245 | + ok($gen->validate('&'), 'Validates &'); |
| 246 | + ok($gen->validate("'"), 'Validates \''); |
| 247 | + |
| 248 | + # Test invalid characters |
| 249 | + ok(!$gen->validate('!'), 'Rejects !'); |
| 250 | + ok(!$gen->validate('('), 'Rejects ('); |
| 251 | + ok(!$gen->validate('a'), 'Rejects a'); |
| 252 | +}; |
| 253 | + |
| 254 | +done_testing(); |
0 commit comments