| #!/usr/bin/perl |
| # |
| # make_tests.pl - generate WPT test cases from the testable statements wiki |
| # |
| # This script assumes that a wiki has testable statement entries |
| # in the format described by the specification at |
| # https://spec-ops.github.io/atta-api/index.html |
| # |
| # usage: make_tests.pl -f file | -w wiki_title | -s spec -d dir |
| |
| use strict; |
| |
| use IO::String ; |
| use JSON ; |
| use MediaWiki::API ; |
| use Getopt::Long; |
| |
| my %specs = ( |
| "aria11" => { |
| title => "ARIA_1.1_Testable_Statements", |
| specURL => "https://www.w3.org/TR/wai-aria11/", |
| dir => "aria11" |
| }, |
| "svg" => { |
| title => "SVG_Accessibility/Testing/Test_Assertions_with_Tables_for_ATTA", |
| specURL => "https://www.w3.org/TR/svg-aam-1.0/", |
| dir => "svg", |
| fragment => '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">%code%</svg>' |
| } |
| ); |
| |
| my @apiNames = qw(UIA MSAA ATK IAccessible2 AXAPI); |
| my $apiNamesRegex = "(" . join("|", @apiNames) . ")"; |
| |
| # the suffix to attach to the automatically generated test case names |
| my $theSuffix = "-manual.html"; |
| |
| # dir is determined based upon the short name of the spec and is defined |
| # by the input or on the command line |
| |
| my $file = undef ; |
| my $spec = undef ; |
| my $wiki_title = undef ; |
| my $dir = undef; |
| my $theSpecFragment = "%code%"; |
| my $preserveWiki = ""; |
| my $fake = 0; |
| |
| my $result = GetOptions( |
| "f|file=s" => \$file, |
| "p=s" => \$preserveWiki, |
| "w|wiki=s" => \$wiki_title, |
| "s|spec=s" => \$spec, |
| "f|fake" => \$fake, |
| "d|dir=s" => \$dir) || usage(); |
| |
| my $wiki_config = { |
| "api_url" => "https://www.w3.org/wiki/api.php" |
| }; |
| |
| my $io ; |
| our $theSpecURL = ""; |
| |
| if ($spec) { |
| print "Processing spec $spec\n"; |
| $wiki_title = $specs{$spec}->{title}; |
| $theSpecURL = $specs{$spec}->{specURL}; |
| if (!$dir) { |
| $dir = "../" . $specs{$spec}->{dir}; |
| } |
| $theSpecFragment = $specs{$spec}->{fragment}; |
| } |
| |
| if (!$dir) { |
| $dir = "../raw"; |
| } |
| |
| if (!-d $dir) { |
| print STDERR "No such directory: $dir\n"; |
| exit 1; |
| } |
| |
| if ($file) { |
| open($io, "<", $file) || die("Failed to open $file: " . $@); |
| } elsif ($wiki_title) { |
| my $MW = MediaWiki::API->new( $wiki_config ); |
| |
| $MW->{config}->{on_error} = \&on_error; |
| |
| sub on_error { |
| print "Error code: " . $MW->{error}->{code} . "\n"; |
| print $MW->{error}->{stacktrace}."\n"; |
| die; |
| } |
| my $page = $MW->get_page( { title => $wiki_title } ); |
| my $theContent = $page->{'*'}; |
| print "Loaded " . length($theContent) . " from $wiki_title\n"; |
| if ($preserveWiki) { |
| if (open(OUTPUT, ">$preserveWiki")) { |
| print OUTPUT $theContent; |
| close OUTPUT; |
| print "Wiki preserved in $preserveWiki\n"; |
| exit 0; |
| } else { |
| print "Failed to create $preserveWiki. Terminating.\n"; |
| exit 1; |
| } |
| } |
| $io = IO::String->new($theContent); |
| } else { |
| usage() ; |
| } |
| |
| |
| |
| # Now let's walk through the content and build a test page for every item |
| # |
| |
| # iterate over the content |
| |
| # my $io ; |
| # open($io, "<", "raw") ; |
| |
| # data structure: |
| # |
| # steps is a list of steps to be performed. |
| # Each step is an object that has a type property and other properties based upon that type. |
| # |
| # Types include: |
| # |
| # 'test' - has a property for each ATAPI for which there are tests |
| # 'attribute' - has a property for the target id, attribute name, and value |
| # 'event' - has a property for the target id and event name |
| my $state = 0; # between items |
| my $theStep = undef; |
| my $current = ""; |
| my $theCode = ""; |
| my $theAttributes = {}; |
| my @steps ; |
| my $theAsserts = {} ; |
| my $theAssertCount = 0; |
| my $theAPI = ""; |
| my $typeRows = 0; |
| my $theType = ""; |
| my $theName = ""; |
| my $theRef = ""; |
| my $lineCounter = 0; |
| my $skipping = 0; |
| |
| our $testNames = {} ; |
| |
| while (<$io>) { |
| if (m/<!-- END OF TESTS -->/) { |
| last; |
| } |
| $lineCounter++; |
| # look for state |
| if (m/^SpecURL: (.*)$/) { |
| $theSpecURL = $1; |
| $theSpecURL =~ s/^ *//; |
| $theSpecURL =~ s/ *$//; |
| } |
| if ($state == 5 && m/^; \/\/ (.*)/) { |
| # we found another test inside a block |
| # we were in an item; dump it |
| build_test($current, $theAttributes, $theCode, \@steps, $theSpecFragment) ; |
| # print "Finished $current and new subblock $1\n"; |
| $state = 1; |
| $theAttributes = {} ; |
| $theAPI = ""; |
| @steps = (); |
| $theCode = ""; |
| $theAsserts = undef; |
| $theName = ""; |
| } elsif (m/^=== +(.*[^ ]) +===/) { |
| if ($state != 0) { |
| if ($skipping) { |
| print STDERR "Flag on assertion $current; skipping\n"; |
| } else { |
| # we were in an item; dump it |
| build_test($current, $theAttributes, $theCode, \@steps, $theSpecFragment) ; |
| # print "Finished $current\n"; |
| } |
| } |
| $state = 1; |
| $current = $1; |
| $theAttributes = {} ; |
| @steps = (); |
| $theCode = ""; |
| $theAsserts = undef; |
| $theAPI = ""; |
| $theName = ""; |
| if ($current =~ m/\(/) { |
| # there is a paren in the name -skip it |
| $skipping = 1; |
| } else { |
| $skipping = 0; |
| } |
| } |
| |
| if ($state == 1) { |
| if (m/<pre>/) { |
| # we are now in the code block |
| $state = 2; |
| next; |
| } elsif (m/==== +(.*) +====/) { |
| # we are in some other block |
| $theName = lc($1); |
| $theAttributes->{$theName} = ""; |
| next; |
| } |
| if (m/^Reference: +(.*)$/) { |
| $theAttributes->{reference} = $theSpecURL . "#" . $1; |
| } elsif ($theName ne "") { |
| # accumulate whatever was in the block under the data for it |
| chomp(); |
| $theAttributes->{$theName} .= $_; |
| } elsif (m/TODO/) { |
| $state = 0; |
| } |
| } |
| |
| if ($state == 2) { |
| if (m/<\/pre>/) { |
| # we are done with the code block |
| $state = 3; |
| } else { |
| if (m/^\s/ && !m/if given/) { |
| # trim any trailing whitespace |
| $theCode =~ s/ +$//; |
| $theCode =~ s/\t/ /g; |
| $theCode .= $_; |
| # In MediaWiki, to display & symbol escapes as literal text, one |
| # must use "&&" for the "&" character. We need to undo that. |
| $theCode =~ s/&(\S)/&$1/g; |
| } |
| } |
| } elsif ($state == 3) { |
| # look for a table |
| if (m/^\{\|/) { |
| # table started |
| $state = 4; |
| } |
| } elsif ($state == 4) { |
| if (m/^\|-/) { |
| if ($theAPI |
| && exists($theAsserts->{$theAPI}->[$theAssertCount]) |
| && scalar(@{$theAsserts->{$theAPI}->[$theAssertCount]})) { |
| $theAssertCount++; |
| } |
| # start of a table row |
| if ($theType ne "" && $typeRows) { |
| # print qq($theType typeRows was $typeRows\n); |
| # we are still processing items for a type |
| $typeRows--; |
| # populate the first cell |
| $theAsserts->{$theAPI}->[$theAssertCount] = [ $theType ] ; |
| } else { |
| $theType = ""; |
| } |
| } elsif (m/^\|\}/) { |
| # ran out of table |
| $state = 5; |
| # adding processing for additional block types |
| # a colspan followed by a keyword triggers a start |
| # so |colspan=5|element triggers a new collection |
| # |colspan=5|attribute triggers the setting of an attribute |
| } elsif (m/^\|colspan="*([0-9])"*\|([^ ]+) (.*)$/) { |
| my $type = $2; |
| my $params = $3; |
| |
| my $obj = {} ; |
| if ($type eq "attribute") { |
| if ($params =~ m/([^:]+):([^ ]+) +(.*)$/) { |
| $obj = { |
| type => $type, |
| element => $1, |
| attribute => $2, |
| value => $3 |
| }; |
| $theStep = undef; |
| push(@steps, $obj); |
| } else { |
| print STDERR "Malformed attribute instruction at line $lineCounter: " . $_ . "\n"; |
| } |
| } elsif ($type eq "event") { |
| if ($params =~ m/([^:]+):([^ ]+).*$/) { |
| $obj = { |
| type => $type, |
| element => $1, |
| value => $2 |
| }; |
| $theStep = undef; |
| push(@steps, $obj); |
| } else { |
| print STDERR "Malformed event instruction at line $lineCounter: " . $_ . "\n"; |
| } |
| } elsif ($type eq "element") { |
| $obj = { |
| type => "test", |
| element => $3 |
| }; |
| push(@steps, $obj); |
| $theStep = scalar(@steps) - 1; |
| $theAsserts = $steps[$theStep]; |
| } else { |
| print STDERR "Unknown operation type: $type at line " . $lineCounter . "; skipping.\n"; |
| } |
| } elsif (m/($apiNamesRegex)$/) { |
| my $theString = $1; |
| $theString =~ s/ +$//; |
| $theString =~ s/^ +//; |
| if ($theString eq "IA2") { |
| $theString = "IAccessible2" ; |
| } |
| my $rows = 1; |
| if (m/^\|rowspan="*([0-9])"*\|(.*)$/) { |
| $rows = $1 |
| } |
| if (grep { $_ eq $theString } @apiNames) { |
| # we found an API name - were we already processing assertions? |
| if (!$theAsserts) { |
| # nope - now what? |
| $theAsserts = { |
| type => "test", |
| element => "test" |
| }; |
| push(@steps, $theAsserts); |
| } |
| $theAssertCount = 0; |
| # this is a new API section |
| $theAPI = $theString ; |
| $theAsserts->{$theAPI} = [ [] ] ; |
| $theType = ""; |
| } else { |
| # this is a multi-row type |
| $theType = $theString; |
| $typeRows = $rows; |
| # print qq(Found multi-row $theString for $theAPI with $typeRows rows\n); |
| $typeRows--; |
| # populate the first cell |
| if ($theAPI |
| && exists($theAsserts->{$theAPI}->[$theAssertCount]) |
| && scalar(@{$theAsserts->{$theAPI}->[$theAssertCount]})) { |
| $theAssertCount++; |
| } |
| $theAsserts->{$theAPI}->[$theAssertCount] = [ $theType ] ; |
| } |
| } elsif (m/^\|(.*)$/) { |
| my $item = $1; |
| $item =~ s/^ *//; |
| $item =~ s/ *$//; |
| $item =~ s/^['"]//; |
| $item =~ s/['"]$//; |
| # add into the data structure for the API |
| if (!exists $theAsserts->{$theAPI}->[$theAssertCount]) { |
| $theAsserts->{$theAPI}->[$theAssertCount] = [ $item ] ; |
| } else { |
| push(@{$theAsserts->{$theAPI}->[$theAssertCount]}, $item); |
| } |
| } |
| } |
| }; |
| |
| if ($state != 0) { |
| build_test($current, $theAttributes, $theCode, \@steps, $theSpecFragment) ; |
| print "Finished $current\n"; |
| } |
| |
| exit 0; |
| |
| # build_test |
| # |
| # create a test file |
| # |
| # attempts to create unique test names |
| |
| sub build_test() { |
| my $title = shift ; |
| my $attrs = shift ; |
| my $code = shift ; |
| my $steps = shift; |
| my $frag = shift ; |
| |
| if ($title eq "") { |
| print STDERR "No name provided!"; |
| return; |
| } |
| |
| if ($frag ne "") { |
| $frag =~ s/%code%/$code/; |
| $code = $frag; |
| } |
| |
| $code =~ s/ +$//m; |
| $code =~ s/\t/ /g; |
| |
| my $title_reference = $title; |
| |
| if ($code eq "") { |
| print STDERR "No code for $title; skipping.\n"; |
| return; |
| } |
| if ( $steps eq {}) { |
| print STDERR "No assertions for $title; skipping.\n"; |
| return; |
| } |
| |
| my $testDef = |
| { "title" => $title, |
| "steps" => [] |
| }; |
| my $stepCount = 0; |
| foreach my $asserts (@$steps) { |
| $stepCount++; |
| my $step = |
| { |
| "type" => $asserts->{"type"}, |
| "title"=> "step " . $stepCount, |
| }; |
| |
| if ($asserts->{type} eq "test") { |
| # everything in the block is about testing an element |
| $step->{"element"} = ( $asserts->{"element"} || "test" ); |
| |
| my $tests = {}; |
| if ($fake) { |
| $tests->{"WAIFAKE"} = [ [ "property", "role", "is", "ROLE_TABLE_CELL" ], [ "property", "interfaces", "contains", "TableCell" ] ]; |
| } |
| foreach my $name (@apiNames) { |
| if (exists $asserts->{$name} && scalar(@{$asserts->{$name}})) { |
| $tests->{$name} = $asserts->{$name}; |
| } |
| }; |
| |
| $step->{test} = $tests; |
| |
| } elsif ($asserts->{type} eq "attribute") { |
| $step->{type} = "attribute"; |
| $step->{element} = $asserts->{"element"}; |
| $step->{attribute} = $asserts->{"attribute"}; |
| $step->{value} = $asserts->{value}; |
| } elsif ($asserts->{type} eq "event") { |
| $step->{type} = "event"; |
| $step->{element} = $asserts->{"element"}; |
| $step->{event} = $asserts->{value}; |
| } else { |
| print STDERR "Invalid step type: " . $asserts->{type} . "\n"; |
| next; |
| } |
| push(@{$testDef->{steps}}, $step); |
| } |
| |
| |
| # populate the rest of the test definition |
| |
| if (scalar(keys(%$attrs))) { |
| while (my $key = each(%$attrs)) { |
| # print "Copying $key \n"; |
| $testDef->{$key} = $attrs->{$key}; |
| } |
| } |
| |
| if (exists $attrs->{reference}) { |
| $title_reference = "<a href='" . $attrs->{reference} . "'>" . $title_reference . "</a>" ; |
| } |
| |
| my $testDef_json = to_json($testDef, { canonical => 1, pretty => 1, utf8 => 1}); |
| |
| my $fileName = $title; |
| $fileName =~ s/\s*$//; |
| $fileName =~ s/\///g; |
| $fileName =~ s/\s+/_/g; |
| $fileName =~ s/[,=:]/_/g; |
| $fileName =~ s/['"]//g; |
| |
| my $count = 2; |
| if ($testNames->{$fileName}) { |
| while (exists $testNames->{$fileName . "_$count"}) { |
| $count++; |
| } |
| $fileName .= "_$count"; |
| } |
| |
| $fileName = lc($fileName); |
| |
| $testNames->{$fileName} = 1; |
| |
| $fileName .= $theSuffix; |
| |
| my $template = qq(<!doctype html> |
| <html> |
| <head> |
| <title>$title</title> |
| <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> |
| <link rel="stylesheet" href="/wai-aria/scripts/manual.css"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/wai-aria/scripts/ATTAcomm.js"></script> |
| <script> |
| setup({explicit_timeout: true, explicit_done: true }); |
| |
| var theTest = new ATTAcomm( |
| $testDef_json |
| ) ; |
| </script> |
| </head> |
| <body> |
| <p>This test examines the ARIA properties for $title_reference.</p> |
| $code |
| <div id="manualMode"></div> |
| <div id="log"></div> |
| <div id="ATTAmessages"></div> |
| </body> |
| </html>); |
| |
| my $file ; |
| |
| if (open($file, ">", "$dir/$fileName")) { |
| print $file $template; |
| print $file "\n"; |
| close $file; |
| } else { |
| print STDERR qq(Failed to create file "$dir/$fileName" $!\n); |
| } |
| |
| return; |
| } |
| |
| sub usage() { |
| print STDERR q(usage: make_tests.pl -f file | -w wiki_title | -s spec [-n -v -d dir ] |
| |
| -s specname - the name of a spec known to the system |
| -w wiki_title - the TITLE of a wiki page with testable statements |
| -f file - the file from which to read |
| |
| -n - do nothing |
| -v - be verbose |
| -d dir - put generated tests in directory dir |
| ); |
| exit 1; |
| } |
| |
| # vim: ts=2 sw=2 ai: |