File Coverage

blib/lib/Test/Parser.pm
Criterion Covered Total %
statement 102 285 35.7
branch 11 104 10.5
condition 9 35 25.7
subroutine 19 44 43.1
pod 17 38 44.7
total 158 506 31.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Test::Parser - Base class for parsing log files from test runs, and
4             displays in an XML syntax.
5              
6             =head1 SYNOPSIS
7              
8             use Test::Parser::MyTest;
9              
10             my $parser = new Test::Parser::MyTest;
11             $parser->parse($text)
12             or die $parser->error(), "\n";
13             printf("Num Errors: %8d\n", $parser->num_errors());
14             printf("Num Warnings: %8d\n", $parser->num_warnings());
15             printf("Num Executed: %8d\n", $parser->num_executed());
16             printf("Num Passed: %8d\n", $parser->num_passed());
17             printf("Num Failed: %8d\n", $parser->num_failed());
18             printf("Num Skipped: %8d\n", $parser->num_skipped());
19              
20             printf("\nErrors:\n");
21             foreach my $err ($parser->errors()) {
22             print $err;
23             }
24              
25             printf("\nWarnings:\n");
26             foreach my $warn ($parser->warnings()) {
27             print $warn;
28             }
29              
30             print $parser->to_xml();
31              
32             =head1 DESCRIPTION
33              
34             This module serves as a common base class for test log parsers. These
35             tools are intended to be able to parse output from a wide variety of
36             tests - including non-Perl tests.
37              
38             The parsers also write the test data into the 'Test Result Publication
39             Interface' (TRPI) XML schema, developed by SpikeSource. See
40             http://www.spikesource.com/testresults/index.jsp?show=trpi-schema
41              
42             =head1 FUNCTIONS
43              
44             =cut
45              
46             package Test::Parser;
47              
48 14     14   27604 use strict;
  14         32  
  14         461  
49 14     14   69 use warnings;
  14         23  
  14         369  
50 14     14   92 use File::Basename;
  14         34  
  14         2430  
51              
52 14         102 use fields qw(
53             code-convention-report
54             coverage-report
55             test
56             num-datum
57             num-column
58             build
59             root
60             url
61             release
62             vendor
63             license
64             summary
65             description
66             platform
67             kernel
68             version
69             testname
70             type
71             path
72             name
73             units
74             warnings
75             errors
76             testcases
77             num_passed
78             num_failed
79             num_skipped
80             outdir
81             format
82             _debug
83 14     14   14407 );
  14         23465  
84              
85 14     14   2766 use vars qw( %FIELDS $VERSION );
  14         28  
  14         845  
86             our $VERSION = '1.7';
87 14     14   92 use constant END_OF_RECORD => 100;
  14         26  
  14         53784  
88              
89             =head2 new()
90              
91             Creates a new Test::Parser object.
92              
93             =cut
94              
95             sub new {
96 4     4 1 17 my $this = shift;
97 4   33     24 my $class = ref($this) || $this;
98 4         90 my $self = bless {%FIELDS}, $class;
99              
100 4         19 $self->{path} = 0;
101 4         9 $self->{units} = $class;
102 4         10 $self->{version} = $class;
103 4         11 $self->{type} = 'unit';
104 4         10 $self->{warnings} = [];
105 4         8 $self->{errors} = [];
106 4         10 $self->{testcases} = [];
107 4         8 $self->{num_passed} = 0;
108 4         8 $self->{num_failed} = 0;
109 4         9 $self->{num_skipped} = 0;
110 4         128 $self->{outdir} = '.';
111 4         60 $self->{format} = 'png';
112 4         10 $self->{_debug} = 0;
113 4         8 $self->{name} = "";
114 4         500 $class=~s/^Test::Parser:://;
115 4         10 $self->{'testname'} = $class;
116 4         8 $self->{'num-column'} = 0;
117 4         8 $self->{'num-datum'} = 0;
118 4         6 $self->{build} = 0;
119 4         8 $self->{root} = 0;
120 4         5 $self->{release} = 0;
121 4         8 $self->{url} = 0;
122 4         6 $self->{vendor} = 0;
123 4         8 $self->{license} = 0;
124 4         6 $self->{summary} = 0;
125 4         8 $self->{description} = 0;
126 4         8 $self->{platform} = 0;
127 4         7 $self->{kernel} = 0;
128 4         9 $self->{'coverage-report'}=0;
129 4         6 $self->{'code-convention-report'}=0;
130            
131 4         51 return $self;
132             }
133              
134             =head2 name()
135              
136             Gets/sets name parameter. user-customizable identification tag
137              
138             =cut
139              
140             sub name {
141 3     3 1 8 my $self = shift;
142 3         6 my $my_name = shift;
143              
144 3 50       13 if ($my_name) {
145 3         8 $self->{name} = $my_name;
146             }
147              
148 3         9 return $self->{name};
149             }
150              
151             =head2 testname()
152              
153             Gets/sets testname parameter.
154              
155             =cut
156              
157             sub testname {
158 0     0 1 0 my $self = shift;
159 0         0 my $testname = shift;
160              
161 0 0       0 if ($testname) {
162 0         0 $self->{testname} = $testname;
163             }
164              
165 0         0 return $self->{testname};
166             }
167              
168             sub version {
169 0     0 0 0 my $self = shift;
170 0         0 my $version = shift;
171              
172 0 0       0 if ( $version ) {
173 0         0 $self->{version} = $version;
174             }
175              
176 0         0 return $self->{version};
177             }
178              
179             sub units {
180 0     0 0 0 my $self = shift;
181 0         0 my $units = shift;
182              
183 0 0       0 if ( $units ) {
184 0         0 $self->{units} = $units;
185             }
186              
187 0         0 return $self->{units};
188             }
189              
190             =head2 to_xml
191              
192             Method to print test result data from the Test::Parser object in xml format following the trpi schema. Find the trpi schema here: http://developer.osdl.org/~jdaiker/trpi_extended_proposal.xsd
193              
194             =cut
195              
196             sub to_xml {
197 0     0 1 0 my $self = shift;
198 0         0 my $xml = "";
199 0         0 my $data = $self->data();
200 0         0 my @required = qw(testname version description summary license vendor release url platform);
201 0         0 my @fields = qw(testname version description summary license vendor release url platform kernel root build coverage-report code-convention-report);
202              
203 0         0 foreach my $field (@required) {
204 0 0       0 if( !$self->{$field} ) {
205 0         0 print "Missing required field: $field\n";
206 0         0 return undef;
207             }
208             }
209 0         0 $xml .= qq|\n|;
210 0         0 foreach my $field (@fields) {
211 0 0       0 if ($self->{$field}) {
212             #Special case for build / status
213 0 0 0     0 if ($field eq 'build' && $self->{build_status}) {
214 0         0 $xml .= qq| $self->{build}\n|;
215             }
216             else {
217 0         0 $xml .= qq| <$field>$self->{$field}\n|;
218             }
219             }
220             }
221 0 0       0 if( $self->{test} ){
222 0         0 $xml .= qq|
223 0 0       0 if( $self->{test}->{'log-filename'} ){
224 0         0 $xml .= qq| log-filename=$self->{test}->{'log-filename'}|;
225             }
226 0 0       0 if( $self->{test}->{path} ){
227 0         0 $xml .= qq| path=$self->{test}->{path}|;
228             }
229 0 0       0 if( $self->{test}->{'suite-type'} ){
230 0         0 $xml .= qq| suite-type=$self->{test}->{'suite-type'}>\n|;
231             }
232             else {
233 0         0 $xml .= qq|>\n|;
234             }
235 0 0       0 if( $self->{test}->{data} ){
236 0         0 $xml .= qq| \n|;
237 0 0       0 if( $self->{test}->{data}->{columns} ){
238 0         0 $xml .= qq| \n|;
239              
240 0         0 my %column_hash=%{$self->{test}->{data}->{columns}};
  0         0  
241 0         0 foreach my $column_key(sort {$a <=> $b} keys %column_hash){
  0         0  
242 0 0       0 if( $column_hash{$column_key}->{'name'} ){
243 0         0 $xml .= qq|
244             }
245 0 0       0 if( $column_hash{$column_key}->{units} ){
246 0         0 $xml .= qq| units="$column_hash{$column_key}->{units}"|;
247             }
248 0         0 $xml .= qq|/>\n|;
249             }
250 0         0 $xml .= qq| \n|;
251             }
252 0 0       0 if( $self->{test}->{data}->{datum} ){
253 0         0 my %datum_hash=%{ $self->{test}->{data}->{datum} };
  0         0  
254 0         0 foreach my $datum_key( sort {$a <=> $b} keys %datum_hash ){
  0         0  
255 0         0 $xml .= qq| \n|;
256 0         0 foreach my $key_val( sort {$a <=> $b} keys %{ $datum_hash{$datum_key} }){
  0         0  
  0         0  
257 0 0       0 if( $key_val ){
258 0         0 $xml .= qq| |;
259 0 0       0 if( $self->{test}->{data}->{datum}->{$datum_key}->{$key_val} ){
260 0         0 $xml .= qq|$self->{test}->{data}->{datum}->{$datum_key}->{$key_val}|;
261             }
262 0         0 $xml .= qq|\n|;
263             }
264             }
265 0         0 $xml .= qq| \n|;
266             }
267             }
268 0         0 $xml .= qq| \n|;
269             }
270 0         0 $xml .= qq| \n|;
271             }
272 0         0 $xml .= qq|\n|;
273 0         0 return $xml;
274             }
275              
276              
277             =head2 add_column
278              
279             A method that adds test column information into the data structure of the Test::Parser object appropriately. This is a helper method to be used from the parse_line method.
280              
281             =cut
282             sub add_column {
283 0     0 1 0 my $self=shift;
284 0         0 my $name=shift;
285 0         0 my $units=shift;
286 0         0 $self->{'num-column'}+=1;
287 0         0 my $columnId = $self->{'num-column'};
288 0         0 $self->{test}->{data}->{columns}->{$columnId}->{name}=$name;
289 0         0 $self->{test}->{data}->{columns}->{$columnId}->{units}=$units;
290 0         0 return $columnId;
291             }
292              
293              
294             =head2 add_data
295              
296             A method that adds data values corresponding to a given column
297              
298             =cut
299             sub add_data {
300 0     0 1 0 my $self = shift;
301 0         0 my $val = shift;
302 0         0 my $col = shift;
303 0         0 my $temp = 1;
304            
305 0 0       0 if ( defined($self->{'num-datum'}) ) {
306 0         0 $temp += $self->{'num-datum'};
307             }
308              
309 0         0 for(my $dumy=1; $dumy<($self->{'num-column'}+1); $dumy+=1){
310 0         0 $self->{test}->{data}->{datum}->{$temp}->{$col}= $val;
311             }
312 0         0 return;
313             }
314              
315              
316             =head2 inc_datum
317              
318             A method that increments the num-datum variable
319              
320             =cut
321             sub inc_datum {
322 0     0 1 0 my $self = shift;
323 0 0       0 if ( defined($self->{'num-datum'}) ) {
324 0         0 $self->{'num-datum'} += 1;
325             }
326             else {
327 0         0 $self->{'num-datum'} = 1;
328             }
329 0         0 return $self->{'num-datum'};
330             }
331              
332              
333             =head2 to_dump()
334              
335             Function to output all data, good for debuging
336              
337             =cut
338             sub to_dump {
339 0     0 1 0 my $self = shift;
340              
341 0         0 require Data::Dumper;
342 0         0 print Data::Dumper->Dumper($self->{test});
343             }
344              
345              
346             =head2 set_debug($debug)
347              
348             Turns on debug level. Set to 0 or undef to turn off.
349              
350             =cut
351             sub num_data {
352 0     0 0 0 my $self =shift;
353 0 0       0 if (@_) {
354 0         0 $self->{num_columns} = @_;
355             }
356 0         0 return $self->{num_columns};
357             }
358              
359             sub build {
360 0     0 0 0 my $self =shift;
361 0 0       0 if (@_) {
362 0         0 $self->{build} = @_;
363             }
364 0         0 return $self->{build};
365             }
366              
367             sub root {
368 0     0 0 0 my $self =shift;
369 0 0       0 if (@_) {
370 0         0 $self->{root} = @_;
371             }
372 0         0 return $self->{root};
373             }
374             sub url {
375 0     0 0 0 my $self =shift;
376 0 0       0 if (@_) {
377 0         0 $self->{url} = @_;
378             }
379 0         0 return $self->{url};
380             }
381              
382             sub release {
383 0     0 0 0 my $self =shift;
384 0 0       0 if (@_) {
385 0         0 $self->{release} = @_;
386             }
387 0         0 return $self->{release};
388             }
389              
390             sub vendor {
391 0     0 0 0 my $self =shift;
392 0 0       0 if (@_) {
393 0         0 $self->{vendor} = @_;
394             }
395 0         0 return $self->{vendor};
396             }
397              
398             sub license {
399 0     0 0 0 my $self =shift;
400 0 0       0 if (@_) {
401 0         0 $self->{license} = @_;
402             }
403 0         0 return $self->{license};
404             }
405              
406             sub summary {
407 0     0 0 0 my $self =shift;
408 0 0       0 if (@_) {
409 0         0 $self->{summary} = @_;
410             }
411 0         0 return $self->{summary};
412             }
413              
414             sub description {
415 0     0 0 0 my $self =shift;
416 0 0       0 if (@_) {
417 0         0 $self->{description} = @_;
418             }
419 0         0 return $self->{description};
420             }
421              
422             sub platform {
423 0     0 0 0 my $self =shift;
424 0 0       0 if (@_) {
425 0         0 $self->{platform} = @_;
426             }
427 0         0 return $self->{platform};
428             }
429              
430             sub type {
431 3     3 1 6 my $self =shift;
432 3 50       16 if (@_) {
433 3         10 $self->{type} = @_;
434             }
435 3         8 return $self->{type};
436             }
437              
438             sub set_debug {
439 0     0 1 0 my $self = shift;
440              
441 0 0       0 if (@_) {
442 0         0 $self->{_debug} = shift;
443             }
444              
445 0         0 return $self->{_debug};
446             }
447              
448             =head3 type()
449              
450             Gets or sets the testsuite type. Valid values include the following:
451             unit, regression, load, integration, boundary, negative, stress, demo, standards
452              
453             =cut
454              
455             sub type_2 {
456 0     0 0 0 my $self =shift;
457 0 0       0 if (@_) {
458 0         0 $self->{type} = @_;
459             }
460 0         0 return $self->{type};
461             }
462              
463             sub path {
464 0     0 0 0 my $self =shift;
465 0 0       0 if (@_) {
466 0         0 $self->{path} = @_;
467             }
468 0         0 return $self->{path};
469             }
470              
471             sub warnings {
472 1     1 1 1 my $self = shift;
473 1 50       4 if (@_) {
474 0         0 $self->{warnings} = shift;
475             }
476 1   50     8 $self->{warnings} ||= [];
477 1         4 return $self->{warnings};
478             }
479              
480             sub num_warnings {
481 1     1 1 2 my $self = shift;
482 1         1 return 0 + @{$self->warnings()};
  1         7  
483             }
484              
485             sub errors {
486 1     1 1 4 my $self = shift;
487 1 50       4 if (@_) {
488 0         0 $self->{errors} = shift;
489             }
490 1   50     11 $self->{errors} ||= [];
491 1         12 return $self->{errors};
492             }
493              
494             sub num_errors {
495 1     1 1 19 my $self = shift;
496 1         2 return 0 + @{$self->errors()};
  1         12  
497             }
498              
499             sub testcases {
500 3     3 0 8 my $self = shift;
501 3 50       14 if (@_) {
502 0         0 $self->{testcases} = shift;
503             }
504 3   50     15 $self->{testcases} ||= [];
505 3         29 return $self->{testcases};
506             }
507              
508             sub num_executed {
509 3     3 0 50 my $self = shift;
510 3         9 return 0 + @{$self->testcases()};
  3         69  
511             }
512              
513             sub num_passed {
514 3     3 0 8 my $self = shift;
515 3         16 return $self->{num_passed};
516             }
517              
518             sub num_failed {
519 3     3 0 6 my $self = shift;
520 3         14 return $self->{num_failed};
521             }
522              
523             sub num_skipped {
524 3     3 0 7 my $self = shift;
525 3         15 return $self->{num_skipped};
526             }
527              
528             sub format {
529 0     0 0 0 my $self = shift;
530 0 0       0 if (@_) {
531 0         0 $self->{format} = shift;
532             }
533 0         0 return $self->{format};
534             }
535              
536             sub outdir {
537 0     0 0 0 my $self = shift;
538 0 0       0 if (@_) {
539 0         0 $self->{outdir} = shift;
540             }
541 0         0 return $self->{outdir};
542             }
543              
544              
545             =head2 get_key
546              
547             Purpose: To find individual key values parsed from test results
548             Input: The search key, the 'datum' the key is stored in
549             Output: Data stored under the search key, or the search key if not found
550              
551             =cut
552             sub get_key {
553 0     0 1 0 my $self = shift;
554 0 0       0 my $key = shift or warn ("No search key specified");
555 0 0       0 my $datum_id = shift or warn ("No datum id specified");
556              
557 0         0 my $col_id = undef;
558            
559 0         0 foreach my $id ( keys %{ $self->{test}->{data}->{columns} } ) {
  0         0  
560 0         0 my $check_key = $self->{test}->{data}->{columns}->{$id}->{name};
561            
562 0 0       0 if( $self->{test}->{data}->{columns}->{$id}->{name} eq $key ) {
563 0         0 $col_id = $id;
564             }
565             }
566            
567 0 0       0 if (defined($col_id)) {
568 0         0 return $self->{test}->{data}->{datum}->{$datum_id}->{$col_id}
569             }
570             else {
571 0         0 warn ("Unable to find key: " . $key . "\n");
572 0         0 return $key;
573             }
574             }
575              
576              
577             =head2 parse($input, [$name[, $path]])
578              
579             Call this routine to perform the parsing process. $input can be any of
580             the following:
581              
582             * A text string
583             * A filename of an external log file to parse
584             * An open file handle (e.g. \*STDIN)
585              
586             If you are dealing with a very large file, then using the filename
587             approach will be more memory efficient. If you wish to use this program
588             in a pipe context, then the file handle style will be more suitable.
589              
590             This routine simply iterates over each newline-separated line of text,
591             calling _parse_line. Note that the default _parse_line() routine does
592             nothing particularly interesting, so you will probably wish to subclass
593             Test::Parser and provide your own implementation of parse_line() to do
594             what you need.
595              
596             The 'name' argument allows you to specify the log filename or other
597             indication of the source of the parsed data. 'path' allows specification
598             of the location of this file within the test run directory. By default,
599             if $input is a filename, 'name' and 'path' will be taken from that, else
600             they'll be left blank.
601              
602             If the filename contains multiple test records, parse() simply parses
603             the first one it finds, and then returns the constant
604             Test::Parser::END_OF_RECORD. If your input file contains multiple
605             records, you probably want to call parse in the GLOB fashion. E.g.,
606              
607             my @logs;
608             open (FILE, 'my.log') or die "Couldn't open: $!\n";
609             while (FILE) {
610             my $parser = new Test::Parser;
611             $parser->parse(\*FILE);
612             push @logs, $parser;
613             }
614             close (FILE) or die "Couldn't close: $!\n";
615              
616             =cut
617              
618             sub parse {
619 4     4 1 33 my $self = shift;
620 4 50       21 my $input = shift or return undef;
621 4         9 my ($name, $path) = @_;
622              
623 4         7 my $retval = 1;
624              
625             # If it's a GLOB, we're probably reading from STDIN
626 4 50 33     144 if (ref($input) eq 'GLOB') {
    50 33        
    50          
627 0         0 while (<$input>) {
628 0   0     0 $retval = $self->parse_line($_) || $retval;
629 0 0       0 last if $retval == END_OF_RECORD;
630             }
631             }
632             # If it's a scalar and has newlines, it's probably the full text
633             elsif (!ref($input) && $input =~ /\n/) {
634 0         0 my @lines = split /\n/, $input;
635 0         0 while (shift @lines) {
636 0   0     0 $retval = $self->parse_line($_) || $retval;
637 0 0       0 last if $retval == END_OF_RECORD;
638             }
639             }
640              
641             # If it appears to be a valid filename, assume we're reading an external file
642             elsif (!ref($input) && -f $input) {
643 4   33     299 $name ||= basename($input);
644 4   33     143 $path ||= dirname($input);
645              
646 4 50 0     174 open (FILE, "< $input")
647             or warn "Could not open '$input' for reading: $!\n"
648             and return undef;
649 4         150 while () {
650 5134   33     12953 $retval = $self->parse_line($_) || $retval;
651 5134 50       18308 last if $retval eq END_OF_RECORD;
652             }
653 4         120 close(FILE);
654             }
655 4         19 $self->{path} = $path;
656              
657 4         21 return $retval;
658             }
659              
660             =head2 parse_line($text)
661              
662             Virtual function for parsing a line of test result data. The base class'
663             implementation of this routine does nothing interesting.
664              
665             You will need to override this routine to customize it to your
666             application. The parse() routine will call this iteratively for each
667             line of text in the test output file.
668              
669             Returns undef on error. The error message can be retrieved via error().
670              
671             =cut
672              
673             sub parse_line {
674 0     0 1   my $self = shift;
675 0 0         my $text = shift or return undef;
676              
677 0           return undef;
678             }
679              
680              
681             =head2 num_warnings()
682              
683             The number of warnings found
684              
685             =head2 warnings()
686              
687             Returns a reference to an array of the warnings encountered.
688              
689             =head2 num_errors()
690              
691             The number of errors found
692              
693             =head2 errors()
694              
695             Returns a reference to an array of the errors encountered.
696              
697             =head1 AUTHOR
698              
699             Bryce Harrington
700              
701             =head1 COPYRIGHT
702              
703             Copyright (C) 2005 Bryce Harrington.
704             All Rights Reserved.
705              
706             This script is free software; you can redistribute it and/or modify it
707             under the same terms as Perl itself.
708              
709             =head1 SEE ALSO
710              
711             L, L
712              
713             =cut
714              
715              
716             1;
717