#! /usr/bin/perl -w # # Copyright (c) 2002 Japan Network Information Center. # All rights reserved. # # By using this file, you agree to the terms and conditions set forth bellow. # # LICENSE TERMS AND CONDITIONS # # The following License Terms and Conditions apply, unless a different # license is obtained from Japan Network Information Center ("JPNIC"), # a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda, # Chiyoda-ku, Tokyo 101-0047, Japan. # # 1. Use, Modification and Redistribution (including distribution of any # modified or derived work) in source and/or binary forms is permitted # under this License Terms and Conditions. # # 2. Redistribution of source code must retain the copyright notices as they # appear in each source code file, this License Terms and Conditions. # # 3. Redistribution in binary form must reproduce the Copyright Notice, # this License Terms and Conditions, in the documentation and/or other # materials provided with the distribution. For the purposes of binary # distribution the "Copyright Notice" refers to the following language: # "Copyright (c) 2000-2002 Japan Network Information Center. All rights reserved." # # 4. The name of JPNIC may not be used to endorse or promote products # derived from this Software without specific prior written approval of # JPNIC. # # 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JPNIC BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. # use FileHandle; use Getopt::Std; # # Parsing status. # my $STATUS_HEADER = 0; my $STATUS_HEADER_COMMENT = 1; my $STATUS_SEPARATOR = 2; my $STATUS_BODY = 3; my $STATUS_GLOBAL = 4; my $STATUS_GLOBAL_COMMENT = 5; my $STATUS_PREAMBLE = 6; my $LINENO_MARK = ""; # # Create a new testsuite context. # sub new_testsuite { return {'ntests' => 0, 'setups' => {}, 'teardowns' => {}, 'tests' => [], 'titles' => [], 'preambles' => ''}; } # # Read `$file' and put the result into `$testsutie'. # sub parse_file { my ($testsuite, $file, $lineinfo) = @_; my $parser = {'type' => '', 'group' => '', 'title' => '', 'status' => $STATUS_PREAMBLE, 'error' => '', 'file' => $file, 'lineno' => 0, 'lineinfo' => $lineinfo}; my $handle = FileHandle->new($file, 'r'); if (!defined($handle)) { die "failed to open the file, $!: $file\n"; } my ($result, $line); for (;;) { $line = $handle->getline(); last if (!defined($line)); chomp($line); $line .= "\n"; $parser->{lineno}++; $result = parse_line($testsuite, $parser, $line); if (!$result) { die sprintf("%s, at line %d\n", $parser->{error}, $parser->{lineno}); } } if ($parser->{status} != $STATUS_GLOBAL) { die "unexpected EOF, at line $.\n"; } $handle->close(); } sub parse_line { my ($testsuite, $parser, $line) = @_; my $result = 1; if ($parser->{status} == $STATUS_HEADER) { if ($line =~ /^\/\/--/) { $parser->{status} = $STATUS_HEADER_COMMENT; } elsif ($line =~ /^\/\//) { $result = parse_header($testsuite, $parser, $line); } elsif ($line =~ /^\s*$/) { $parser->{status} = $STATUS_SEPARATOR; $result = parse_endheader($testsuite, $parser, $line); } elsif ($line =~ /^\{\s*$/) { $parser->{status} = $STATUS_BODY; $result = parse_endheader($testsuite, $parser, $line) && parse_startbody($testsuite, $parser, $line); } else { $parser->{error} = 'syntax error'; $result = 0; } } elsif ($parser->{status} == $STATUS_HEADER_COMMENT) { if ($line =~ /^\/\//) { # nothing to be done. } elsif ($line =~ /^\s*$/) { $parser->{status} = $STATUS_SEPARATOR; $result = parse_endheader($testsuite, $parser, $line); } elsif ($line =~ /^\{\s*$/) { $parser->{status} = $STATUS_BODY; $result = parse_endheader($testsuite, $parser, $line) && parse_startbody($testsuite, $parser, $line); } else { $parser->{error} = 'syntax error'; $result = 0; } } elsif ($parser->{status} == $STATUS_SEPARATOR) { if ($line =~ /^\s*$/) { # nothing to be done. } elsif ($line =~ /^\{\s*$/) { $parser->{status} = $STATUS_BODY; $result = parse_startbody($testsuite, $parser, $line); } else { $parser->{error} = 'syntax error'; $result = 0; } } elsif ($parser->{status} == $STATUS_BODY) { if ($line =~ /^\}\s*$/) { $parser->{status} = $STATUS_GLOBAL; $result = parse_endbody($testsuite, $parser, $line); } else { $result = parse_body($testsuite, $parser, $line); } } elsif ($parser->{status} == $STATUS_GLOBAL) { if ($line =~ /^\/\/\#/) { $parser->{status} = $STATUS_HEADER; $result = parse_startheader($testsuite, $parser, $line); } elsif ($line =~ /^\/\/--/) { $parser->{status} = $STATUS_GLOBAL_COMMENT; } elsif ($line =~ /^\s*$/) { # nothing to be done. } else { $parser->{error} = 'syntax error'; $result = 0; } } elsif ($parser->{status} == $STATUS_GLOBAL_COMMENT) { if ($line =~ /^\/\//) { # nothing to be done. } elsif ($line =~ /^\s*$/) { $parser->{status} = $STATUS_GLOBAL; } else { $parser->{error} = 'syntax error'; $result = 0; } } elsif ($parser->{status} == $STATUS_PREAMBLE) { if ($line =~ /^\/\/\#/) { $parser->{status} = $STATUS_HEADER; $result = parse_startheader($testsuite, $parser, $line); } elsif ($line =~ /^\/\/--/) { $parser->{status} = $STATUS_GLOBAL_COMMENT; } else { $result = parse_preamble($testsuite, $parser, $line); } } else { $parser->{error} = 'syntax error'; $result = 0; } return $result; } sub parse_startheader { my ($testsuite, $parser, $line) = @_; if ($line =~ /^\/\/\#\s*(SETUP|TEARDOWN|TESTCASE)\s*$/) { $parser->{type} = $1; $parser->{group} = ''; $parser->{title} = ''; } else { $parser->{error} = 'invalid test-header format'; return 0; } return 1; } sub parse_header { my ($testsuite, $parser, $line) = @_; my $field = $line; $field =~ s/^\/\/\s*//; $field =~ s/^(\S+):\s*/$1:/; $field =~ s/\s+$//; return 1 if ($field eq ''); if ($field =~ /^group:(.*)$/) { my $group = $1; if ($parser->{group} ne '') { $parser->{error} = "group defined twice in a header"; return 0; } if ($parser->{type} eq 'SETUP') { if ($group !~ /^[0-9A-Za-z_\-]+$/) { $parser->{error} = "invalid group name"; return 0; } if (defined($testsuite->{setups}->{$group})) { $parser->{error} = sprintf("SETUP \`%s' redefined", $group); return 0; } } elsif ($parser->{type} eq 'TEARDOWN') { if ($group !~ /^[0-9A-Za-z_\-]+$/) { $parser->{error} = "invalid group name"; return 0; } if (defined($testsuite->{teardowns}->{$group})) { $parser->{error} = sprintf("TEARDOWN \`%s' redefined", $group); return 0; } } else { foreach my $i (split(/[ \t]+/, $group)) { if ($i !~ /^[0-9A-Za-z_\-]+$/) { $parser->{error} = "invalid group name \`$i'"; return 0; } if (!defined($testsuite->{setups}->{$i}) && !defined($testsuite->{teardowns}->{$i})) { $parser->{error} = sprintf("group \'%s' not defined", $i); return 0; } } } $parser->{group} = $group; } elsif ($field =~ /^title:(.*)$/) { my $title = $1; if ($parser->{title} ne '') { $parser->{error} = "title defined twice in a header"; return 0; } if ($title =~ /[\x00-\x1f\x7f-\xff\"\\]/ || $title eq '') { $parser->{error} = "invalid title"; return 0; } if ($parser->{type} ne 'TESTCASE') { $parser->{error} = sprintf("title for %s is not permitted", $parser->{type}); return 0; } $parser->{title} = $title; } else { $parser->{error} = "invalid test-header field"; return 0; } return 1; } sub parse_endheader { my ($testsuite, $parser, $line) = @_; if ($parser->{type} ne 'TESTCASE' && $parser->{group} eq '') { $parser->{error} = "missing \`group' in the header"; return 0; } if ($parser->{type} eq 'TESTCASE' && $parser->{title} eq '') { $parser->{error} = "missing \`title' in the header"; return 0; } return 1; } sub parse_startbody { my ($testsuite, $parser, $line) = @_; my $group = $parser->{group}; if ($parser->{type} eq 'SETUP') { if ($parser->{lineinfo}) { $testsuite->{setups}->{$group} = generate_line_info($parser->{lineno} + 1, $parser->{file}); } } elsif ($parser->{type} eq 'TEARDOWN') { if ($parser->{lineinfo}) { $testsuite->{teardowns}->{$group} = generate_line_info($parser->{lineno} + 1, $parser->{file}); } } else { $testsuite->{ntests}++; push(@{$testsuite->{tests}}, ''); push(@{$testsuite->{titles}}, $parser->{title}); $testsuite->{tests}->[-1] .= "\n"; $testsuite->{tests}->[-1] .= "$LINENO_MARK\n"; $testsuite->{tests}->[-1] .= sprintf("static void\ntestcase\%d(idn_testsuite_t ctx__) {\n", $testsuite->{ntests}); my (@group_names) = split(/[ \t]+/, $group); for (my $i = 0; $i < @group_names; $i++) { if (defined($testsuite->{setups}->{$group_names[$i]})) { $testsuite->{tests}->[-1] .= "\t\{\n"; $testsuite->{tests}->[-1] .= "#undef EXIT__\n"; $testsuite->{tests}->[-1] .= "#define EXIT__ exit${i}__\n"; $testsuite->{tests}->[-1] .= $testsuite->{setups}->{$group_names[$i]}; } } $testsuite->{tests}->[-1] .= "$LINENO_MARK\n"; $testsuite->{tests}->[-1] .= "\t\{\n"; $testsuite->{tests}->[-1] .= "#undef EXIT__\n"; $testsuite->{tests}->[-1] .= "#define EXIT__ exit__\n"; if ($parser->{lineinfo}) { $testsuite->{tests}->[-1] .= generate_line_info($parser->{lineno} + 1, $parser->{file}); } } return 1; } sub parse_body { my ($testsuite, $parser, $line) = @_; my ($group) = $parser->{group}; if ($parser->{type} eq 'SETUP') { $testsuite->{setups}->{$group} .= $line; } elsif ($parser->{type} eq 'TEARDOWN') { $testsuite->{teardowns}->{$group} .= $line; } else { $testsuite->{tests}->[-1] .= $line; } return 1; } sub parse_endbody { my ($testsuite, $parser, $line) = @_; my ($group) = $parser->{group}; if ($parser->{type} eq 'TESTCASE') { $testsuite->{tests}->[-1] .= "$LINENO_MARK\n"; $testsuite->{tests}->[-1] .= "\t\}\n"; $testsuite->{tests}->[-1] .= " exit__:\n"; $testsuite->{tests}->[-1] .= "\t;\n"; my (@group_names) = split(/[ \t]+/, $group); for (my $i = @group_names - 1; $i >= 0; $i--) { $testsuite->{tests}->[-1] .= " exit${i}__:\n"; $testsuite->{tests}->[-1] .= "\t;\n"; if (defined($testsuite->{teardowns}->{$group_names[$i]})) { $testsuite->{tests}->[-1] .= $testsuite->{teardowns}->{$group_names[$i]}; } $testsuite->{tests}->[-1] .= "$LINENO_MARK\n"; $testsuite->{tests}->[-1] .= "\t\}\n"; } $testsuite->{tests}->[-1] .= "}\n"; } return 1; } sub parse_preamble { my ($testsuite, $parser, $line) = @_; if ($parser->{lineinfo} && $parser->{lineno} == 1) { $testsuite->{preambles} .= generate_line_info(1, $parser->{file}); } $testsuite->{preambles} .= $line; return 1; } sub generate_line_info { my ($lineno, $file) = @_; return "#line $lineno \"$file\"\n"; } # # Output `$testsuite' as source codes of C. # sub output_tests { my ($testsuite, $file, $lineinfo) = @_; my $generator = { 'file' => $file, 'lineno' => 0 }; my $handle = FileHandle->new($file, 'w'); if (!defined($handle)) { die "failed to open the file, $!: $file\n"; } my $preamble_header = "/* This file is automatically generated by testygen. */\n\n" . "#define TESTYGEN 1\n" . "\n"; output_lines($preamble_header, $generator, $handle, $lineinfo); output_lines($testsuite->{preambles}, $generator, $handle, $lineinfo); my $preamble_footer = "\n" . "$LINENO_MARK\n" . "#include \"testsuite.h\"\n" . "\n"; output_lines($preamble_footer, $generator, $handle, $lineinfo); for (my $i = 0; $i < $testsuite->{ntests}; $i++) { output_lines($testsuite->{tests}->[$i], $generator, $handle, $lineinfo); } my $main_header = "\n" . "$LINENO_MARK\n" . "int\n" . "main(int argc, char *argv[]) {\n" . "\tidn_testsuite_t ctx;\n" . "\tconst char *title;\n" . "\n" . "\tidn_testsuite_create(&ctx);\n"; output_lines($main_header, $generator, $handle, $lineinfo); for (my $i = 0; $i < $testsuite->{ntests}; $i++) { my $title = $testsuite->{titles}->[$i]; my $proc = sprintf("testcase%d", $i + 1); output_lines("\tidn_testsuite_addtestcase(ctx, \"$title\", $proc);\n", $generator, $handle, $lineinfo); } my $main_footer = "\n" . "\tif (argc > 1 && strcmp(argv[1], \"-v\") == 0) {\n" . "\t idn_testsuite_setverbose(ctx);\n" . "\t argc--;\n" . "\t argv++;\n" . "\t}\n" . "\tif (argc == 1)\n" . "\t idn_testsuite_runall(ctx);\n" . "\telse\n" . "\t idn_testsuite_run(ctx, argv + 1);\n" . "\n" . "\tprintf(\"passed=%d, failed=%d, total=%d\\n\",\n" . "\t idn_testsuite_npassed(ctx),\n" . "\t idn_testsuite_nfailed(ctx),\n" . "\t idn_testsuite_ntestcases(ctx) - idn_testsuite_nskipped(ctx));\n" . "\n" . "\tidn_testsuite_destroy(ctx);\n" . "\treturn (0);\n" . "\}\n"; output_lines($main_footer, $generator, $handle, $lineinfo); $handle->close(); } sub output_lines { my ($lines, $generator, $handle, $lineinfo) = @_; my ($line); chomp($lines); $lines .= "\n"; while ($lines ne '') { $lines =~ s/^([^\n]*)\n//; $line = $1; $generator->{lineno}++; if ($line eq $LINENO_MARK) { if ($lineinfo) { $handle->printf("#line %d \"%s\"\n", $generator->{lineno} + 1, $generator->{file}); } } else { $handle->print("$line\n"); } } } sub output_usage { warn "$0: [-o output-file] input-file\n"; } # # main. # my (%options); if (!getopts('Lo:', \%options)) { output_usage; exit(1); } if (@ARGV != 1) { output_usage; exit(1); } my ($in_file) = $ARGV[0]; my ($out_file); if (!defined($options{o})) { $out_file = $in_file; $out_file .= '\.tsy' if ($out_file !~ /\.tsy$/); $out_file =~ s/\.tsy$/\.c/; } else { $out_file = $options{o}; } my $testsuite = new_testsuite(); parse_file($testsuite, $in_file, !$options{L}); output_tests($testsuite, $out_file, !$options{L}); exit(0);