File: Model.pm

package info (click to toggle)
libtest-tap-model-perl 0.09-1
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 172 kB
  • ctags: 299
  • sloc: perl: 917; makefile: 588
file content (570 lines) | stat: -rw-r--r-- 12,378 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
#!/usr/bin/perl

package Test::TAP::Model;
use base qw/Test::Harness::Straps/;

use strict;
use warnings;

use Test::TAP::Model::File;

use List::Util qw/sum/;

our $VERSION = "0.09";

# callback handlers
sub _handle_bailout {
	my($self, $line, $type, $totals) = @_;

	$self->log_event(
		type => 'bailout',
		($self->{bailout_reason}
			? (reason => $self->{bailout_reason})
			: ()
		),
	);

	$self->{meat}{test_files}[-1]{results} = $totals;

	die "Bailed out"; # catch with an eval { }
}
        
sub _handle_test {
	my($self, $line, $type, $totals) = @_;
	my $curr = $totals->seen || 0;

	# this is used by pugs' Test.pm, it's rather useful
	my $pos;
	if ($line =~ /^(.*?) <pos:(.*)>(\r?$|\s*#.*\r?$)/){
		$line = $1 . $3;
		$pos = $2;
	}

	my %details = %{ $totals->details->[-1] };

	$self->log_event(
		type      => 'test',
		num       => $curr,
		ok        => $details{ok},
		actual_ok => $details{actual_ok},
		str       => $details{ok} # string for people
		             	? "ok $curr/" . $totals->max
		             	: "NOK $curr",
		todo      => ($details{type} eq 'todo'),
		skip      => ($details{type} eq 'skip'),

		reason    => $details{reason}, # if at all

		# pugs aux stuff
		line      => $line,
		pos       => $pos,
	);

	if( $curr > $self->{'next'} ) {
		$self->latest_event->{note} =
			"Test output counter mismatch [test $curr]\n";
	}
	elsif( $curr < $self->{'next'} ) {
		$self->latest_event->{note} = join("",
			"Confused test output: test $curr answered after ",
					  "test ", ($self->{'next'}||0) - 1, "\n");
	}
}

sub _handle_other {
	my($self, $line, $type, $totals) = @_;

	my $last_test = $self->{meat}{test_files}[-1];
	if (@{ $last_test->{events} ||= [] } > 0) {
		($self->latest_event->{diag} ||= "") .= "$line\n";
	} else {
		($last_test->{pre_diag} ||= "") .= "$line\n";
	}
}

sub new_with_tests {
	my $pkg = shift;
	my @tests = @_;

	my $self = $pkg->new;
	$self->run_tests(@tests);

	$self;
}

sub new_with_struct {
	my $pkg = shift;
	my $meat = shift;

	my $self = $pkg->new(@_);
	$self->{meat} = $meat; # FIXME - the whole Test::Harness::Straps model can be figured out from this

	$self;
}

sub structure {
	my $self = shift;
	$self->{meat};
}

# just a dispatcher for the above event handlers
sub _init {
	my $s = shift;

	$s->{callback} = sub {
		my($self, $line, $type, $totals) = @_;

		my $meth = "_handle_$type";
		$self->$meth($line, $type, $totals) if $self->can($meth);
	};

	$s->SUPER::_init( @_ );
}

sub log_time {
	my $self = shift;
	$self->{log_time} = shift if @_;
	$self->{log_time};
}

sub log_event {
	my $self = shift;
	my %event = (($self->log_time ? (time => time) : ()), @_);

	push @{ $self->{events} }, \%event;

	\%event;
}

sub latest_event {
	my($self) = shift;
        my %event = @_;
        $self->{events}[-1] || $self->log_event(%event);  
}

sub run {
	my $self = shift;
	$self->run_tests($self->get_tests);
}

sub get_tests {
	die 'the method get_tests is a stub. You must implement it yourself if you want $self->run to work.';
}

sub run_tests {
	my $self = shift;

	$self->_init;

	$self->{meat}{start_time} = time;

	foreach my $file (@_) {
		$self->run_test($file);
	}

	$self->{meat}{end_time} = time;
}

sub run_test {
	my $self = shift;
	my $file = shift;

	my $test_file = $self->start_file($file);
	
	my $results = eval { $self->analyze_file($file) } || Test::Harness::Results->new;
	$test_file->{results} = $results;
	$test_file->{results}->details(undef); # we don't need that

	$test_file;
}

sub start_file {
	my $self = shift;
	my $file = shift;

	push @{ $self->{meat}{test_files} }, my $test_file = {
		file => $file,
		events => ($self->{events} = []),
	};

	$test_file;
}

sub file_class { "Test::TAP::Model::File" }	

sub test_files {
	my $self = shift;
	@{$self->{_test_files_cache} ||= [ $self->get_test_files ]};
}

sub get_test_files {
	my $self = shift;
	map { $self->file_class->new($_) } @{ $self->{meat}{test_files} };
}

sub ok { $_->ok or return for $_[0]->test_files; 1 }; *passed = \&ok; *passing = \&ok;
sub nok { !$_[0]->ok }; *failing = \&nok; *failed = \&nok;
sub total_ratio { return $_ ? $_[0]->total_passed / $_ : ($_[0]->ok ? 1 : 0) for $_[0]->total_seen }; *ratio = \&total_ratio;
sub total_percentage { sprintf("%.2f%%", 100 * $_[0]->total_ratio) }
sub total_seen { sum map { scalar $_->seen } $_[0]->test_files }
sub total_todo { sum map { scalar $_->todo_tests } $_[0]->test_files }
sub total_skipped { sum map { scalar $_->skipped_tests } $_[0]->test_files }
sub total_passed { sum map { scalar $_->ok_tests } $_[0]->test_files }; *total_ok = \&total_passed;
sub total_failed { sum map { scalar $_->nok_tests } $_[0]->test_files }; *total_nok = \&total_failed;
sub total_unexpectedly_succeeded { sum map { scalar $_->unexpectedly_succeeded_tests } $_[0]->test_files }

sub summary {
	my $self = shift;
	$self->{_summary} ||=
	sprintf "%d test cases: %d ok, %d failed, %d todo, "
			."%d skipped and %d unexpectedly succeeded",
			map { my $m = "total_$_"; $self->$m }
			qw/seen passed failed todo skipped unexpectedly_succeeded/;
}

__PACKAGE__

__END__

=pod

=head1 NAME

Test::TAP::Model - Accessible (queryable, serializable object) result collector
for L<Test::Harness::Straps> runs.

=head1 SYNOPSIS

	use Test::TAP::Model;

	my $t = Test::TAP::Model->new();

	# Test::Harness::Straps methods are available, but they aren't enough.
	# Extra book keeping is required. See the run_test method

	# here's a convenient wrapper
	$t = Test::TAP::Model->new_with_tests(glob("t/*.t"));
	
	# that's shorthand for new->run_tests
	$t->run_tests(qw{ t/foo.t t/bar.t });

	# every file is an object (Test::TAP::Model::File)
	my @tests = $t->test_files;

	# this method returns a structure
	my $structure = $t->structure;

	# which is guaranteed to survive serialization
	my $other_struct = do { my $VAR; eval Data::Dumper::Dumper($structure) };

	# the same as $t1
	my $t2 = Test::TAP::Model->new_with_struct($other_struct);

=head1 DESCRIPTION

This module is a subclass of L<Test::Harness::Straps> (although in an ideal
world it would really use delegation).

It uses callbacks in the straps object to construct a deep structure, with all
the data known about a test run accessible within.

It's purpose is to ease the processing of test data, for the purpose of
generating reports, or something like that.

The niche it fills is creating a way to access test run data, both from a
serialized and a real source, and to ease the querying of this data.

=head1 YEAH YEAH, WHAT IS IT GOOD FOR?

Well, you can use it to send test results, and process them into pretty
reports. See L<Test::TAP::HTMLMatrix>.

=head1 TWO INTERFACES

There are two ways to access the data in L<Test::TAP::Model>. The complex one,
which creates objects, revolves around the simpler one, which for Q&D purposes
is exposed and encouraged too.

Inside the object there is a well defined deep structure, accessed as

	$t->structure;

This is the simple method. It is a hash, containing some fields, and basically
organizes the test results, with all the fun fun data exposed.

The second interface is documented below in L</METHODS>, and lets you create
pretty little objects from this structure, which might or might not be more
convenient for your purposes.

When it's ready, that is.

=head1 HASH STRUCTURE

I hope this illustrates how the structure looks.

	$structure = {
		test_files => $test_files,

		start_time => # when the run started
		end_time   => # ... and ended
	};

	$test_files = [
		$test_file,
		...
	];

	$test_file = {
		file => "t/filename.t",
		results => \%results;
		events => $events,

		# optional
		pre_diag => # diagnosis emitted before any test
	};

	%results = $strap->analyze_foo(); 

	$events = [
		{
			type => "test",
			num    => # the serial number of the test
			ok     => # a boolean
			result => # a string useful for display
			todo   => # a boolean
			line   => # the output line

			# pugs auxillery stuff, from the <pos:> comment
			pos    => # the place in the test file the case is in

			time   => # the time this event happenned
		},
		{
			type => "bailout",
			reason => "blah blah blah",
		}
		...,
	];

That's basically it.

=head1 OBJECT INTERFACE

The object interface is structured around three objects:

=over 4

=item L<Test::TAP::Model>

A whole run

=item L<Test::TAP::Model::File>

A test script

=item L<Test::TAP::Model::Subtest>

A single case in a test script

=back

Each of these is discussed in it's respectful manpage. Here's the whole run:

=head1 METHODS

=head2 The said OOP interface

=over 4

=item test_files

Returns a list of L<Test::TAP::Model::File> objects.

=item ok

=item passing

=item passed

=item nok

=item failed

=item failing

Whether all the suite was OK, or opposite.

=item total_ok

=item total_passed

=item total_nok

=item total_failed

=item total_percentage

=item total_ratio

=item total_seen

=item total_skipped

=item total_todo

=item total_unexpectedly_succeeded

These methods are all rather self explanatory and either provide aggregate
results based on the contained test files.

=item ratio

An alias to total_ratio.

=back

=head2 Misc methods

=over 4

=item new

Creates an empty harness.

=item new_with_struct $struct

Adopts a structure. This is how you take a thawed structure and query it.

=item new_with_tests @tests

Takes a list of tests and immediately runs them.

=item get_tests

A method invoked by C<run> to get a list of tests to run.

This is a stub, and you should subclass it if you care.

=item run

This method runs the list of tests returned by C<get_tests>.

=item run_tests @tests

Runs these tests. Just loops, and calls analyze file, with an eval { } around
it to catch bail out.

=item run_test $test

Actually this is the part which does eval and calls C<start_file> and
C<analyze_file>

=item start_file

This tells L<Test::TAP::Model> that we are about to analyze a new file.

This will eventually be moved into an overridden version of analyze, I think.

Consider it's existence a bug.

=item log_event

This logs a new event with time stamp in the event log for the current test.

=item latest_event

Returns the hash ref to the last event, or a new one if there isn't a last
event yet.

=item file_class

This method returns the class to call new on, when generating file objects in
C<test_files>.

=item structure

This method returns the hash reference you can save, browse, or use to create
new objects with the same date.

=item log_time

This is an accessor. If it's value is set to true, any events logged will have
a time stamp added.

=back

=head1 SERIALIZING

You can use any serializer you like (L<YAML>, L<Storable>, etc), to freeze C<<
$obj->structure >>, and then you can thaw it back, and pass the thawed
structure to C<new_with_struct>.

You can then access the object interface normally.

This behavior is guaranteed to remain consistent, at least between similar
versions of this module. This is there to simplify smoke reports.

=head1 ISA Test::Harness::Straps

L<Test::TAP::Model> is a L<Test::Harness::Straps> subclass. It knows to run
tests on it's own. See the C<run> methods and it's friends.

However, you should see how C<run_test> gets things done beforehand. It's a bit
of a hack because I'm not quite sure if L<Test::Harness::Straps> has the proper
events to encapsulate this cleanly (Gaal took care of the handlers way before I
got into the picture), and I'm too lazy to check it out.

=head1 VERSION CONTROL

This module is maintained using Darcs. You can get the latest version from
L<http://nothingmuch.woobling.org/Test-TAP-Model/>, and use C<darcs send> to
commit changes.

=head1 AUTHORS

This list was generated from svn log testgraph.pl and testgraph.css in the pugs
repo, sorted by last name.

=over 4

=item *

Michal Jurosz

=item *

Yuval Kogman <nothingmuch@woobling.org> NUFFIN

=item *

Max Maischein <corion@cpan.org> CORION

=item *

James Mastros <james@mastros.biz> JMASTROS

=item *

Scott McWhirter <scott-cpan@NOSPAMkungfuftr.com> KUNGFUFTR

=item *

putter (svn handle)

=item *

Audrey Tang <cpan@audreyt.org> AUDREYT

=item *

Gaal Yahas <gaal@forum2.org> GAAL

=back

=head1 COPYRIGHT & LICNESE

	Copyright (c) 2005 the aforementioned authors. All rights
	reserved. This program is free software; you can redistribute
	it and/or modify it under the same terms as Perl itself.

=cut