make_tests.pl (14174B)
1 #!/usr/bin/perl 2 # 3 # make_tests.pl - generate WPT test cases from the testable statements wiki 4 # 5 # This script assumes that a wiki has testable statement entries 6 # in the format described by the specification at 7 # https://spec-ops.github.io/atta-api/index.html 8 # 9 # usage: make_tests.pl -f file | -w wiki_title | -s spec -d dir 10 11 use strict; 12 13 use IO::String ; 14 use JSON ; 15 use MediaWiki::API ; 16 use Getopt::Long; 17 18 my %specs = ( 19 "aria11" => { 20 title => "ARIA_1.1_Testable_Statements", 21 specURL => "https://www.w3.org/TR/wai-aria11/", 22 dir => "aria11" 23 }, 24 "svg" => { 25 title => "SVG_Accessibility/Testing/Test_Assertions_with_Tables_for_ATTA", 26 specURL => "https://www.w3.org/TR/svg-aam-1.0/", 27 dir => "svg", 28 fragment => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">%code%</svg>' 29 } 30 ); 31 32 my @apiNames = qw(UIA MSAA ATK IAccessible2 AXAPI); 33 my $apiNamesRegex = "(" . join("|", @apiNames) . ")"; 34 35 # the suffix to attach to the automatically generated test case names 36 my $theSuffix = "-manual.html"; 37 38 # dir is determined based upon the short name of the spec and is defined 39 # by the input or on the command line 40 41 my $file = undef ; 42 my $spec = undef ; 43 my $wiki_title = undef ; 44 my $dir = undef; 45 my $theSpecFragment = "%code%"; 46 my $preserveWiki = ""; 47 my $fake = 0; 48 49 my $result = GetOptions( 50 "f|file=s" => \$file, 51 "p=s" => \$preserveWiki, 52 "w|wiki=s" => \$wiki_title, 53 "s|spec=s" => \$spec, 54 "f|fake" => \$fake, 55 "d|dir=s" => \$dir) || usage(); 56 57 my $wiki_config = { 58 "api_url" => "https://www.w3.org/wiki/api.php" 59 }; 60 61 my $io ; 62 our $theSpecURL = ""; 63 64 if ($spec) { 65 print "Processing spec $spec\n"; 66 $wiki_title = $specs{$spec}->{title}; 67 $theSpecURL = $specs{$spec}->{specURL}; 68 if (!$dir) { 69 $dir = "../" . $specs{$spec}->{dir}; 70 } 71 $theSpecFragment = $specs{$spec}->{fragment}; 72 } 73 74 if (!$dir) { 75 $dir = "../raw"; 76 } 77 78 if (!-d $dir) { 79 print STDERR "No such directory: $dir\n"; 80 exit 1; 81 } 82 83 if ($file) { 84 open($io, "<", $file) || die("Failed to open $file: " . $@); 85 } elsif ($wiki_title) { 86 my $MW = MediaWiki::API->new( $wiki_config ); 87 88 $MW->{config}->{on_error} = \&on_error; 89 90 sub on_error { 91 print "Error code: " . $MW->{error}->{code} . "\n"; 92 print $MW->{error}->{stacktrace}."\n"; 93 die; 94 } 95 my $page = $MW->get_page( { title => $wiki_title } ); 96 my $theContent = $page->{'*'}; 97 print "Loaded " . length($theContent) . " from $wiki_title\n"; 98 if ($preserveWiki) { 99 if (open(OUTPUT, ">$preserveWiki")) { 100 print OUTPUT $theContent; 101 close OUTPUT; 102 print "Wiki preserved in $preserveWiki\n"; 103 exit 0; 104 } else { 105 print "Failed to create $preserveWiki. Terminating.\n"; 106 exit 1; 107 } 108 } 109 $io = IO::String->new($theContent); 110 } else { 111 usage() ; 112 } 113 114 115 116 # Now let's walk through the content and build a test page for every item 117 # 118 119 # iterate over the content 120 121 # my $io ; 122 # open($io, "<", "raw") ; 123 124 # data structure: 125 # 126 # steps is a list of steps to be performed. 127 # Each step is an object that has a type property and other properties based upon that type. 128 # 129 # Types include: 130 # 131 # 'test' - has a property for each ATAPI for which there are tests 132 # 'attribute' - has a property for the target id, attribute name, and value 133 # 'event' - has a property for the target id and event name 134 my $state = 0; # between items 135 my $theStep = undef; 136 my $current = ""; 137 my $theCode = ""; 138 my $theAttributes = {}; 139 my @steps ; 140 my $theAsserts = {} ; 141 my $theAssertCount = 0; 142 my $theAPI = ""; 143 my $typeRows = 0; 144 my $theType = ""; 145 my $theName = ""; 146 my $theRef = ""; 147 my $lineCounter = 0; 148 my $skipping = 0; 149 150 our $testNames = {} ; 151 152 while (<$io>) { 153 if (m/<!-- END OF TESTS -->/) { 154 last; 155 } 156 $lineCounter++; 157 # look for state 158 if (m/^SpecURL: (.*)$/) { 159 $theSpecURL = $1; 160 $theSpecURL =~ s/^ *//; 161 $theSpecURL =~ s/ *$//; 162 } 163 if ($state == 5 && m/^; \/\/ (.*)/) { 164 # we found another test inside a block 165 # we were in an item; dump it 166 build_test($current, $theAttributes, $theCode, \@steps, $theSpecFragment) ; 167 # print "Finished $current and new subblock $1\n"; 168 $state = 1; 169 $theAttributes = {} ; 170 $theAPI = ""; 171 @steps = (); 172 $theCode = ""; 173 $theAsserts = undef; 174 $theName = ""; 175 } elsif (m/^=== +(.*[^ ]) +===/) { 176 if ($state != 0) { 177 if ($skipping) { 178 print STDERR "Flag on assertion $current; skipping\n"; 179 } else { 180 # we were in an item; dump it 181 build_test($current, $theAttributes, $theCode, \@steps, $theSpecFragment) ; 182 # print "Finished $current\n"; 183 } 184 } 185 $state = 1; 186 $current = $1; 187 $theAttributes = {} ; 188 @steps = (); 189 $theCode = ""; 190 $theAsserts = undef; 191 $theAPI = ""; 192 $theName = ""; 193 if ($current =~ m/\(/) { 194 # there is a paren in the name -skip it 195 $skipping = 1; 196 } else { 197 $skipping = 0; 198 } 199 } 200 201 if ($state == 1) { 202 if (m/<pre>/) { 203 # we are now in the code block 204 $state = 2; 205 next; 206 } elsif (m/==== +(.*) +====/) { 207 # we are in some other block 208 $theName = lc($1); 209 $theAttributes->{$theName} = ""; 210 next; 211 } 212 if (m/^Reference: +(.*)$/) { 213 $theAttributes->{reference} = $theSpecURL . "#" . $1; 214 } elsif ($theName ne "") { 215 # accumulate whatever was in the block under the data for it 216 chomp(); 217 $theAttributes->{$theName} .= $_; 218 } elsif (m/TODO/) { 219 $state = 0; 220 } 221 } 222 223 if ($state == 2) { 224 if (m/<\/pre>/) { 225 # we are done with the code block 226 $state = 3; 227 } else { 228 if (m/^\s/ && !m/if given/) { 229 # trim any trailing whitespace 230 $theCode =~ s/ +$//; 231 $theCode =~ s/\t/ /g; 232 $theCode .= $_; 233 # In MediaWiki, to display & symbol escapes as literal text, one 234 # must use "&&" for the "&" character. We need to undo that. 235 $theCode =~ s/&(\S)/&$1/g; 236 } 237 } 238 } elsif ($state == 3) { 239 # look for a table 240 if (m/^\{\|/) { 241 # table started 242 $state = 4; 243 } 244 } elsif ($state == 4) { 245 if (m/^\|-/) { 246 if ($theAPI 247 && exists($theAsserts->{$theAPI}->[$theAssertCount]) 248 && scalar(@{$theAsserts->{$theAPI}->[$theAssertCount]})) { 249 $theAssertCount++; 250 } 251 # start of a table row 252 if ($theType ne "" && $typeRows) { 253 # print qq($theType typeRows was $typeRows\n); 254 # we are still processing items for a type 255 $typeRows--; 256 # populate the first cell 257 $theAsserts->{$theAPI}->[$theAssertCount] = [ $theType ] ; 258 } else { 259 $theType = ""; 260 } 261 } elsif (m/^\|\}/) { 262 # ran out of table 263 $state = 5; 264 # adding processing for additional block types 265 # a colspan followed by a keyword triggers a start 266 # so |colspan=5|element triggers a new collection 267 # |colspan=5|attribute triggers the setting of an attribute 268 } elsif (m/^\|colspan="*([0-9])"*\|([^ ]+) (.*)$/) { 269 my $type = $2; 270 my $params = $3; 271 272 my $obj = {} ; 273 if ($type eq "attribute") { 274 if ($params =~ m/([^:]+):([^ ]+) +(.*)$/) { 275 $obj = { 276 type => $type, 277 element => $1, 278 attribute => $2, 279 value => $3 280 }; 281 $theStep = undef; 282 push(@steps, $obj); 283 } else { 284 print STDERR "Malformed attribute instruction at line $lineCounter: " . $_ . "\n"; 285 } 286 } elsif ($type eq "event") { 287 if ($params =~ m/([^:]+):([^ ]+).*$/) { 288 $obj = { 289 type => $type, 290 element => $1, 291 value => $2 292 }; 293 $theStep = undef; 294 push(@steps, $obj); 295 } else { 296 print STDERR "Malformed event instruction at line $lineCounter: " . $_ . "\n"; 297 } 298 } elsif ($type eq "element") { 299 $obj = { 300 type => "test", 301 element => $3 302 }; 303 push(@steps, $obj); 304 $theStep = scalar(@steps) - 1; 305 $theAsserts = $steps[$theStep]; 306 } else { 307 print STDERR "Unknown operation type: $type at line " . $lineCounter . "; skipping.\n"; 308 } 309 } elsif (m/($apiNamesRegex)$/) { 310 my $theString = $1; 311 $theString =~ s/ +$//; 312 $theString =~ s/^ +//; 313 if ($theString eq "IA2") { 314 $theString = "IAccessible2" ; 315 } 316 my $rows = 1; 317 if (m/^\|rowspan="*([0-9])"*\|(.*)$/) { 318 $rows = $1 319 } 320 if (grep { $_ eq $theString } @apiNames) { 321 # we found an API name - were we already processing assertions? 322 if (!$theAsserts) { 323 # nope - now what? 324 $theAsserts = { 325 type => "test", 326 element => "test" 327 }; 328 push(@steps, $theAsserts); 329 } 330 $theAssertCount = 0; 331 # this is a new API section 332 $theAPI = $theString ; 333 $theAsserts->{$theAPI} = [ [] ] ; 334 $theType = ""; 335 } else { 336 # this is a multi-row type 337 $theType = $theString; 338 $typeRows = $rows; 339 # print qq(Found multi-row $theString for $theAPI with $typeRows rows\n); 340 $typeRows--; 341 # populate the first cell 342 if ($theAPI 343 && exists($theAsserts->{$theAPI}->[$theAssertCount]) 344 && scalar(@{$theAsserts->{$theAPI}->[$theAssertCount]})) { 345 $theAssertCount++; 346 } 347 $theAsserts->{$theAPI}->[$theAssertCount] = [ $theType ] ; 348 } 349 } elsif (m/^\|(.*)$/) { 350 my $item = $1; 351 $item =~ s/^ *//; 352 $item =~ s/ *$//; 353 $item =~ s/^['"]//; 354 $item =~ s/['"]$//; 355 # add into the data structure for the API 356 if (!exists $theAsserts->{$theAPI}->[$theAssertCount]) { 357 $theAsserts->{$theAPI}->[$theAssertCount] = [ $item ] ; 358 } else { 359 push(@{$theAsserts->{$theAPI}->[$theAssertCount]}, $item); 360 } 361 } 362 } 363 }; 364 365 if ($state != 0) { 366 build_test($current, $theAttributes, $theCode, \@steps, $theSpecFragment) ; 367 print "Finished $current\n"; 368 } 369 370 exit 0; 371 372 # build_test 373 # 374 # create a test file 375 # 376 # attempts to create unique test names 377 378 sub build_test() { 379 my $title = shift ; 380 my $attrs = shift ; 381 my $code = shift ; 382 my $steps = shift; 383 my $frag = shift ; 384 385 if ($title eq "") { 386 print STDERR "No name provided!"; 387 return; 388 } 389 390 if ($frag ne "") { 391 $frag =~ s/%code%/$code/; 392 $code = $frag; 393 } 394 395 $code =~ s/ +$//m; 396 $code =~ s/\t/ /g; 397 398 my $title_reference = $title; 399 400 if ($code eq "") { 401 print STDERR "No code for $title; skipping.\n"; 402 return; 403 } 404 if ( $steps eq {}) { 405 print STDERR "No assertions for $title; skipping.\n"; 406 return; 407 } 408 409 my $testDef = 410 { "title" => $title, 411 "steps" => [] 412 }; 413 my $stepCount = 0; 414 foreach my $asserts (@$steps) { 415 $stepCount++; 416 my $step = 417 { 418 "type" => $asserts->{"type"}, 419 "title"=> "step " . $stepCount, 420 }; 421 422 if ($asserts->{type} eq "test") { 423 # everything in the block is about testing an element 424 $step->{"element"} = ( $asserts->{"element"} || "test" ); 425 426 my $tests = {}; 427 if ($fake) { 428 $tests->{"WAIFAKE"} = [ [ "property", "role", "is", "ROLE_TABLE_CELL" ], [ "property", "interfaces", "contains", "TableCell" ] ]; 429 } 430 foreach my $name (@apiNames) { 431 if (exists $asserts->{$name} && scalar(@{$asserts->{$name}})) { 432 $tests->{$name} = $asserts->{$name}; 433 } 434 }; 435 436 $step->{test} = $tests; 437 438 } elsif ($asserts->{type} eq "attribute") { 439 $step->{type} = "attribute"; 440 $step->{element} = $asserts->{"element"}; 441 $step->{attribute} = $asserts->{"attribute"}; 442 $step->{value} = $asserts->{value}; 443 } elsif ($asserts->{type} eq "event") { 444 $step->{type} = "event"; 445 $step->{element} = $asserts->{"element"}; 446 $step->{event} = $asserts->{value}; 447 } else { 448 print STDERR "Invalid step type: " . $asserts->{type} . "\n"; 449 next; 450 } 451 push(@{$testDef->{steps}}, $step); 452 } 453 454 455 # populate the rest of the test definition 456 457 if (scalar(keys(%$attrs))) { 458 while (my $key = each(%$attrs)) { 459 # print "Copying $key \n"; 460 $testDef->{$key} = $attrs->{$key}; 461 } 462 } 463 464 if (exists $attrs->{reference}) { 465 $title_reference = "<a href='" . $attrs->{reference} . "'>" . $title_reference . "</a>" ; 466 } 467 468 my $testDef_json = to_json($testDef, { canonical => 1, pretty => 1, utf8 => 1}); 469 470 my $fileName = $title; 471 $fileName =~ s/\s*$//; 472 $fileName =~ s/\///g; 473 $fileName =~ s/\s+/_/g; 474 $fileName =~ s/[,=:]/_/g; 475 $fileName =~ s/['"]//g; 476 477 my $count = 2; 478 if ($testNames->{$fileName}) { 479 while (exists $testNames->{$fileName . "_$count"}) { 480 $count++; 481 } 482 $fileName .= "_$count"; 483 } 484 485 $fileName = lc($fileName); 486 487 $testNames->{$fileName} = 1; 488 489 $fileName .= $theSuffix; 490 491 my $template = qq(<!doctype html> 492 <html> 493 <head> 494 <title>$title</title> 495 <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> 496 <link rel="stylesheet" href="/wai-aria/scripts/manual.css"> 497 <script src="/resources/testharness.js"></script> 498 <script src="/resources/testharnessreport.js"></script> 499 <script src="/wai-aria/scripts/ATTAcomm.js"></script> 500 <script> 501 setup({explicit_timeout: true, explicit_done: true }); 502 503 var theTest = new ATTAcomm( 504 $testDef_json 505 ) ; 506 </script> 507 </head> 508 <body> 509 <p>This test examines the ARIA properties for $title_reference.</p> 510 $code 511 <div id="manualMode"></div> 512 <div id="log"></div> 513 <div id="ATTAmessages"></div> 514 </body> 515 </html>); 516 517 my $file ; 518 519 if (open($file, ">", "$dir/$fileName")) { 520 print $file $template; 521 print $file "\n"; 522 close $file; 523 } else { 524 print STDERR qq(Failed to create file "$dir/$fileName" $!\n); 525 } 526 527 return; 528 } 529 530 sub usage() { 531 print STDERR q(usage: make_tests.pl -f file | -w wiki_title | -s spec [-n -v -d dir ] 532 533 -s specname - the name of a spec known to the system 534 -w wiki_title - the TITLE of a wiki page with testable statements 535 -f file - the file from which to read 536 537 -n - do nothing 538 -v - be verbose 539 -d dir - put generated tests in directory dir 540 ); 541 exit 1; 542 } 543 544 # vim: ts=2 sw=2 ai: