File Coverage

blib/lib/Test/Unit/GTestRunner.pm
Criterion Covered Total %
statement 29 70 41.4
branch 0 6 0.0
condition 0 6 0.0
subroutine 11 30 36.6
pod n/a
total 40 112 35.7


line stmt bran cond sub pod time code
1             #! /bin/false
2              
3             # $Id: GTestRunner.pm.in,v 1.70 2006/05/12 12:42:14 guido Exp $
4              
5             # Copyright (C) 2004-2006 Guido Flohr ,
6             # all rights reserved.
7              
8             # This program is free software; you can redistribute it and/or modify
9             # it under the terms of the GNU General Public License as published by
10             # the Free Software Foundation; either version 2, or (at your option)
11             # any later version.
12              
13             # This program is distributed in the hope that it will be useful,
14             # but WITHOUT ANY WARRANTY; without even the implied warranty of
15             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16             # Library General Public License for more details.
17              
18             # You should have received a copy of the GNU General Public License
19             # along with this program; if not, write to the Free Software Foundation,
20             # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21              
22             package Test::Unit::GTestRunner;
23              
24 1     1   990 use strict;
  1         2  
  1         37  
25              
26 1     1   5 use constant DEBUG => 0;
  1         2  
  1         82  
27              
28 1     1   5 use vars qw ($VERSION $PERL @MY_INC);
  1         8  
  1         96  
29             $VERSION = '0.04';
30              
31             package Test::Unit::GTestRunner::Node;
32              
33 1     1   6 use strict;
  1         1  
  1         2262  
34              
35             sub new {
36 0     0     my ($class, %args) = @_;
37              
38 0           my $self = {
39             __path => $args{path},
40             __name => $args{name},
41             __is_test => $args{is_test},
42             __output => '',
43             __result => '',
44             };
45              
46 0           bless $self, $class;
47             }
48              
49             sub addOutput {
50 0     0     my ($self, $text) = @_;
51              
52 0           $self->{__output} .= $text;
53              
54 0           return 1;
55             }
56              
57             sub setOutput {
58 0     0     my ($self, $text) = @_;
59              
60 0           $self->{__output} = $text;
61              
62 0           return 1;
63             }
64              
65             sub getOutput {
66 0     0     shift->{__output};
67             }
68              
69             sub getStockID {
70 0     0     my ($self) = @_;
71              
72 0           my $stock_id = '';
73              
74 0 0         $stock_id = 'gtk-dialog-question'
75             if $self->isTest;
76 0 0         $stock_id = 'gtk-dialog-warning'
77             if length $self->getOutput;
78 0 0 0       $stock_id = 'gtk-dialog-error'
79             if ($self->isError || $self->isFailure);
80              
81 0           return $stock_id;
82             }
83              
84             sub getName {
85 0     0     shift->{__name};
86             }
87              
88             sub getPath {
89 0     0     shift->{__path};
90             }
91              
92             sub isError {
93 0     0     shift->{__error};
94             }
95              
96             sub setError {
97 0     0     my ($self) = @_;
98              
99 0           $self->{__errror} = 1;
100             }
101              
102             sub isTest {
103 0     0     shift->{__is_test};
104             }
105              
106             sub isFailure {
107 0     0     shift->{__failure};
108             }
109              
110             sub setFailure {
111 0     0     my ($self) = @_;
112              
113 0           $self->{__failure} = 1;
114             }
115              
116             sub isSuccess {
117 0     0     my $self = shift;
118              
119 0   0       return (!$self->isError && !$self->isFailure);
120             }
121              
122             sub setSuccess {
123 0     0     my ($self) = shift;
124              
125 0           delete $self->{__error};
126 0           delete $self->{__failure};
127 0           $self->{__result} = '';
128              
129 0           return;
130             }
131              
132             sub getResult {
133 0     0     shift->{__result};
134             }
135              
136             sub setResult {
137 0     0     my ($self, $result) = @_;
138              
139 0           $self->{__result} = $result;
140             }
141              
142             sub getFailurePath {
143 0     0     shift->{__failure_path};
144             }
145              
146             sub setFailurePath {
147 0     0     my ($self, $path) = @_;
148              
149 0           $self->{__failure_path} = $path;
150             }
151              
152             sub unsetFailurePath {
153 0     0     delete shift->{__failure_path};
154             }
155              
156             package Test::Unit::GTestRunner;
157              
158 1     1   23324 use English qw (-no_match_vars);
  1         14562  
  1         6  
159             BEGIN {
160 1     1   576 $PERL = $EXECUTABLE_NAME; # Aka $^X.
161 1         31 @MY_INC = @INC;
162             }
163              
164 1     1   1071 use Locale::TextDomain qw (Test-Unit-GTestRunner);
  1         30150  
  1         9  
165 1         106 use Locale::Messages qw (bind_textdomain_filter bind_textdomain_codeset
166 1     1   42843 turn_utf_8_on);
  1         2  
167             BEGIN {
168 1     1   8 bind_textdomain_filter 'Test-Unit-GTestRunner', \&turn_utf_8_on;
169 1         12 bind_textdomain_codeset 'Test-Unit-GTestRunner', 'utf-8';
170             }
171              
172 1     1   1900 use MIME::Base64 qw (decode_base64);
  1         1466  
  1         83  
173 1     1   3841 use Test::Unit::GTestRunner::Worker;
  0            
  0            
174             use Config;
175             use Gtk2;
176             use Gtk2::GladeXML;
177             use Storable qw (thaw);
178              
179             use constant GUI_STATES => {
180             initial => {
181             run_menu_item => 0,
182             run_button => 0,
183             run_selected_menu_item => 0,
184             run_selected_button => 0,
185             open_menu_item => 1,
186             open_button => 1,
187             cancel_menu_item => 0,
188             cancel_button => 0,
189             refresh_menu_item => 0,
190             refresh_button => 0,
191             },
192             loaded => {
193             run_menu_item => 1,
194             run_button => 1,
195             run_selected_menu_item => 0,
196             run_selected_button => 0,
197             open_menu_item => 1,
198             open_button => 1,
199             cancel_menu_item => 0,
200             cancel_button => 0,
201             refresh_menu_item => 1,
202             refresh_button => 1,
203             },
204             loaded_selected => {
205             run_menu_item => 1,
206             run_button => 1,
207             run_selected_menu_item => 1,
208             run_selected_button => 1,
209             open_menu_item => 1,
210             open_button => 1,
211             cancel_menu_item => 0,
212             cancel_button => 0,
213             refresh_menu_item => 1,
214             refresh_button => 1,
215             },
216             running => {
217             run_menu_item => 0,
218             run_button => 0,
219             run_selected_menu_item => 0,
220             run_selected_button => 0,
221             open_menu_item => 0,
222             open_button => 0,
223             cancel_menu_item => 1,
224             cancel_button => 1,
225             refresh_menu_item => 0,
226             refresh_button => 0,
227             }
228             };
229              
230             sub new {
231             my $class = shift;
232              
233             my $self = {
234             __counter => 0,
235             __errors => 0,
236             __pid => 0,
237             __kill_signals => [],
238             __last_signal => undef,
239             __failures => [],
240             __new_file_chooser => 0,
241             };
242              
243             # Should we make sure that init is called only once?
244             Gtk2->init;
245              
246             local $/;
247             my $data = ;
248              
249             # It seems that libglade does not consider our call to
250             # bind_textdomain(). We therefore roll our own version.
251             my $gettext = $__;
252             $data =~ s{
253             [ ]translatable="yes">([^<]+)
254             }{
255             my $string = $1;
256             $string =~ s/"/\"/g;
257             $string =~ s/'/\'/g;
258             $string =~ s/</
259             $string =~ s/>/>/g;
260             $string =~ s/&/&/g;
261             $string = $gettext->{$string};
262             $string =~ s/&/&/g;
263             $string =~ s/>/>/g;
264             $string =~ s/
265             $string =~ s/\'/'/g;
266             $string =~ s/\"/"/g;
267             qq{ translatable="no">$string
268             }gex;
269              
270             my $gladexml = Gtk2::GladeXML->new_from_buffer ($data);
271              
272             bless $self, $class;
273              
274             $gladexml->signal_autoconnect_from_package ($self);
275              
276             $self->{__gladexml} = $gladexml;
277              
278             my $statusbar = $self->{__statusbar} =
279             $gladexml->get_widget ('statusbar1');
280             my $context_id = $self->{__context_id} =
281             $statusbar->get_context_id (__PACKAGE__);
282             $statusbar->push ($context_id, ' ' . __"Starting GTestRunner.");
283              
284             my $error_textview = $self->{__error_textview} =
285             $gladexml->get_widget ('errortextview');
286             my $error_textbuffer = Gtk2::TextBuffer->new;
287             my $tag = $error_textbuffer->create_tag (
288             'error',
289             foreground => 'red',
290             'foreground-set' => 1,
291             weight => 600,
292             );
293              
294             $error_textview->set_buffer ($error_textbuffer);
295             $error_textview->set_wrap_mode ('word');
296             $self->{__error_textbuffer} = $error_textbuffer;
297              
298             my $progress_bar = $self->{__progress_bar} =
299             $gladexml->get_widget ('progressbar');
300             my $progress_image = $self->{__progress_image} =
301             $gladexml->get_widget ('progressimage');
302              
303             $self->{__green} = Gtk2::Gdk::Color->new (0, 65535, 0);
304             $self->{__red} = Gtk2::Gdk::Color->new (65535, 0, 0);
305              
306             my $failure_view = $gladexml->get_widget ('failure_treeview');
307             my $failure_store = Gtk2::ListStore->new ('Glib::String',
308             'Glib::String',
309             'Glib::String',
310             'Glib::String');
311             $failure_view->set_model ($failure_store);
312             $failure_view->get_selection->set_mode ('multiple');
313             $self->{__failure_store} = $failure_store;
314             $self->{__failure_view} = $failure_view;
315              
316             my $column = Gtk2::TreeViewColumn->new;
317             $column->set_title (__"Test");
318             $failure_view->append_column ($column);
319              
320             my $count = 0;
321             my $pixbuf_renderer = Gtk2::CellRendererPixbuf->new;
322             $column->pack_start ($pixbuf_renderer, 0);
323             $column->add_attribute ($pixbuf_renderer, 'stock-id' => $count++);
324              
325             my $text_renderer = Gtk2::CellRendererText->new;
326             $column->pack_start ($text_renderer, 1);
327             $column->add_attribute ($text_renderer, text => $count++);
328              
329             for my $header (__"Test Case", __"Source") {
330             my $renderer = Gtk2::CellRendererText->new;
331             $column =
332             Gtk2::TreeViewColumn->new_with_attributes ($header, $renderer,
333             text => $count++);
334             $column->set_resizable (1);
335             $column->set_expand (1);
336             $failure_view->append_column ($column);
337             }
338              
339             $failure_view->signal_connect (cursor_changed =>
340             sub {
341             $self->__onFailureChange (@_);
342             });
343              
344             my $hierarchy_view = $gladexml->get_widget ('hierarchy_treeview');
345             my $hierarchy_store = Gtk2::TreeStore->new ('Glib::String',
346             'Glib::String');
347             $hierarchy_view->get_selection->set_mode ('multiple');
348             $hierarchy_view->set_model ($hierarchy_store);
349             $self->{__hierarchy_store} = $hierarchy_store;
350             $self->{__hierarchy_view} = $hierarchy_view;
351              
352             $hierarchy_view->signal_connect (
353             cursor_changed =>
354             sub {
355             $self->__onHierarchyChange (@_);
356             });
357              
358             $hierarchy_view->signal_connect (
359             row_activated =>
360             sub {
361             $self->__onHierarchyActivated (@_);
362             });
363              
364             $column = Gtk2::TreeViewColumn->new;
365             $column->set_title (__"Test");
366             $hierarchy_view->append_column ($column);
367              
368             $pixbuf_renderer = Gtk2::CellRendererPixbuf->new;
369             $column->pack_start ($pixbuf_renderer, 0);
370             $column->add_attribute ($pixbuf_renderer, 'stock-id' => 1);
371              
372             $text_renderer = Gtk2::CellRendererText->new;
373             $column->pack_start ($text_renderer, 1);
374             $column->add_attribute ($text_renderer, text => 0);
375              
376             # It would be sufficient to set this up only once, but we want
377             # to avoid both a global and complication.
378             $self->{__kill_signals} = [];
379             if ($Config{sig_name}) {
380             my $i = 0;
381             my %signo = ();
382             foreach my $name (split / +/, $Config{sig_name}) {
383             $signo{$name} = $i if ($name eq 'TERM'
384             || $name eq 'QUIT'
385             || $name eq 'KILL');
386             ++$i;
387             }
388             my @killers;
389             push @killers, [ TERM => $signo{TERM} ] if $signo{TERM};
390             push @killers, [ QUIT => $signo{QUIT} ] if $signo{QUIT};
391             push @killers, [ KILL => $signo{KILL} ] if $signo{KILL};
392             $self->{__kill_signals} = \@killers;
393             }
394              
395             my $notebook = $gladexml->get_widget ('notebook');
396             $notebook->signal_connect (switch_page =>
397             sub {
398             $self->__onSwitchPage (@_);
399             });
400              
401             my $check = $gladexml->get_widget ('always_refresh_checkbutton');
402             $check->set_active (1);
403             $self->__refreshSuitesBeforeEveryRun ($check);
404              
405             # Otherwise a zero kill will report on Zombies.
406             $SIG{CHLD} = 'IGNORE' if exists $SIG{CHLD};
407              
408             return $self;
409             }
410              
411             sub start {
412             my ($self, @args) = @_;
413              
414             $self->{__suites} = [@args];
415             $self->__loadSuite if @args;
416              
417             if (@args) {
418             $self->__setGUIState ('loaded');
419             } else {
420             $self->__setGUIState ('initial');
421             }
422            
423             Gtk2->main;
424              
425             return 1;
426             }
427              
428             sub main {
429             Test::Unit::GTestRunner->new->start (@_);
430             }
431              
432             sub __runTests {
433             my ($self, $suites) = @_;
434              
435             Glib::Source->remove ($self->{__timeout_id}) if $self->{__timeout_id};
436              
437             $self->__setGUIState ('running');
438              
439             $self->__setErrorTextBuffer ('');
440             $self->{__progress_bar}->set_fraction (0);
441             $self->{__failure_store}->clear;
442             $self->{__failures} = [];
443             $self->{__error_count} = 0;
444             $self->{__failure_count} = 0;
445             $self->{__counter} = 0;
446             $self->{__num_planned} = @{$self->{__planned}};
447             $self->{__progress_image}->set_from_stock ('gtk-dialog-question',
448             'button');
449              
450             foreach my $node (@{$self->{__planned}}) {
451             $node->setOutput ('');
452             $node->setResult ('');
453             }
454              
455             foreach my $node (@{$self->{__nodes}}) {
456             $node->unsetFailurePath;
457             }
458              
459             my @suites = $suites ? @{$suites} : @{$self->{__suites}};
460              
461             my $arg = join ', ', map {'"' . $_ . '"'} @suites;
462             my @local_inc = map { '-I' . $_ } @MY_INC;
463             local *CMD;
464             my @cmd = ($PERL,
465             @local_inc,
466             '-MTest::Unit::GTestRunner::Worker',
467             #'-d:ptkdb',
468             '-e',
469             "Test::Unit::GTestRunner::Worker->new->start ($arg)",
470             );
471              
472             unless (open CMD, '-|', @cmd) {
473             my $cmd = pop @cmd;
474              
475             foreach my $part (@cmd) {
476             my $arg = quotemeta $part;
477             $cmd .= " $part";
478             }
479              
480             my $msg = __x ("Test cannot be started: {cmd}: {err}.",
481             cmd => $cmd, err => $!);
482             $self->__setErrorTextBuffer ($msg);
483             return;
484             }
485             $self->{__cmd_fileno} = fileno CMD;
486             $self->{__cmd_fh} = *CMD;
487              
488             $self->__setStatusBar (__"Running ...");
489              
490             $self->{__timeout_id} = Glib::Timeout->add (40, sub {
491             $self->__handleReply;
492             return 1;
493             });
494              
495             return 1;
496             }
497              
498             sub __runAllTests {
499             my ($self) = @_;
500              
501             $self->__loadSuite if $self->{__always_refresh};
502              
503             my @planned;
504             foreach my $node (@{$self->{__nodes}}) {
505             push @planned, $node if $node->isTest;
506             }
507              
508             $self->{__planned} = \@planned;
509              
510             return $self->__runTests;
511             }
512              
513             sub __runSelectedTests {
514             my $self = shift;
515             my $dirty;
516              
517             $self->__loadSuite (\$dirty) if $self->{__always_refresh};
518             if ($dirty) {
519             my $message = __<
520             The test suite you want to run has changed. Please make a new
521             selection, and run again.
522             EOF
523             my $main_window = $self->{__gladexml}->get_widget ('GTestRunner');
524             my $dialog = Gtk2::MessageDialog->new ($main_window,
525             'destroy-with-parent',
526             'error',
527             'ok',
528             $message);
529             $dialog->run;
530             $dialog->destroy;
531             return;
532             }
533              
534             my $hierarchy_view = $self->{__hierarchy_view};
535             my $failure_view = $self->{__failure_view};
536              
537             my $hierarchy_paths = [$hierarchy_view->get_selection->get_selected_rows];
538             my $failure_paths = [$failure_view->get_selection->get_selected_rows];
539              
540             if ($failure_paths) {
541             # Convert them into hierarchy_paths.
542             for (my $i = 0; $i < @$failure_paths; $i++) {
543             my $index = $failure_paths->[$i]->to_string;
544             my $node = $self->{__failures}->[0 + $index];
545              
546             $failure_paths->[$i] =
547             Gtk2::TreePath->new_from_string ($node->getPath);
548             }
549             }
550              
551             my $paths = $failure_paths ?
552             @$hierarchy_paths > @$failure_paths
553             ? $hierarchy_paths
554             : $failure_paths
555             : $hierarchy_paths;
556              
557             my @modules;
558             my @planned;
559             my %planned;
560             foreach my $path (@$paths) {
561             my $path_str = $path->to_string;
562              
563             my $node = $self->{__nodes_by_path}->{$path_str};
564              
565             my $module;
566             my $store = $self->{__hierarchy_store};
567             my $iterator = $store->get_iter ($path);
568            
569             if ($node->isTest) {
570             $path_str =~ /:([0-9]+)$/;
571             my $testno = $1;
572             $path->up;
573            
574             ($module) = $store->get ($store->get_iter ($path));
575             # The number serves as the identifier for our worker thread
576             # here. Remember that Perl module names cannot start with
577             # a number.
578             $module .= "::$testno";
579             } else {
580             ($module) = $store->get ($store->get_iter ($path));
581             }
582             push @modules, $module;
583              
584             my $path_len = length $path_str;
585             foreach my $node (@{$self->{__nodes}}) {
586             my $node_path = $node->getPath;
587              
588             if ($path_str eq substr $node_path, 0, $path_len) {
589             # If a subnode of an already selected node is also
590             # selected, we have to avoid to run tests twice.
591             next if $planned{$node_path};
592              
593             # Skip entities which aren't tests.
594             next unless $node->{__is_test};
595            
596             push @planned, $node;
597             $planned{$node_path} = 1;
598             }
599             }
600             }
601              
602             $self->{__planned} = \@planned;
603             return $self->__runTests (\@modules);
604             }
605              
606             sub __terminateTests {
607             my ($self, $message) = @_;
608              
609             $self->__setStatusBar ($message) if defined $message;
610              
611             $self->__sendKill;
612              
613             return 1;
614             }
615              
616             sub __cancelTests {
617             shift->__terminateTests (__"Waiting for test to terminate ...");
618             }
619              
620             sub __refreshSuite {
621             my $self = shift;
622            
623             $self->__setStatusBar (__"Refreshing the test suite.");
624             $self->__loadSuite;
625             }
626              
627             sub __refreshSuitesBeforeEveryRun {
628             my ($self, $check) = @_;
629            
630             my $gladexml = $self->{__gladexml};
631              
632             my $active = $self->{__always_refresh} = $check->get_active;
633              
634             $gladexml->get_widget ('always_refresh_checkbutton')->set_active ($active);
635             $gladexml->get_widget ('always_refresh_menuitem')->set_active ($active);
636              
637             return 1;
638             }
639              
640             sub __loadSuite {
641             my ($self, $dirty_flag) = @_;
642              
643             $self->{__failure_store}->clear;
644              
645             my $store = Gtk2::TreeStore->new ('Glib::String',
646             'Glib::String');
647             my $old_store = $self->{__hierarchy_store};
648              
649             $self->__setErrorTextBuffer ('');
650              
651             my @suites = @{$self->{__suites}};
652              
653             foreach my $suite (@suites) {
654             $suite =~ s/\'/\\\'/g;
655             $suite = "'$suite'";
656             }
657              
658             my $arg = join ', ', @suites;
659             my @local_inc = map { '-I' . $_ } @MY_INC;
660             local *CMD;
661             my @cmd = ($PERL,
662             @local_inc,
663             '-MTest::Unit::GTestRunner::Lister',
664             # '-d:ptkdb',
665             '-e',
666             "Test::Unit::GTestRunner::Lister->new->list ($arg)",
667             );
668              
669             unless (open CMD, '-|', @cmd) {
670             my $cmd = pop @cmd;
671              
672             foreach my $part (@cmd) {
673             my $arg = quotemeta $part;
674             $cmd .= " $part";
675             }
676              
677             my $msg = __x ("Testsuite cannot be listed: {cmd}: {err}.",
678             cmd => $cmd, err => $!);
679             $self->__setErrorTextBuffer ($msg);
680             return;
681             }
682              
683             my @lines = ;
684             my $status = shift @lines;
685             unless (defined $status && $status eq "SUCCESS\n") {
686             $self->__setErrorTextBuffer (join "\n", @lines);
687             $self->__resetGUI;
688             return;
689             }
690             # (void)
691             close CMD;
692              
693             my @indices;
694             my $dirty;
695             my @nodes;
696             foreach my $line (@lines) {
697             chomp $line;
698              
699             unless ($line =~ /^( *)([-+])([A-Za-z0-9_:]+)$/) {
700             $self->__setErrorTextBuffer (__x ("Corrupt test listing: {line}\n",
701             line => $line));
702             $self->__resetGUI;
703             return;
704             }
705              
706             my ($spaces, $type, $name) = ($1, $2, $3);
707             my $depth = length $spaces;
708            
709             unless ($depth <= (1 + @indices)) {
710             my $old_depth = @indices;
711             my $message =
712             __x ("Invalid change in test depth ({old} to {new}).",
713             old => $old_depth, new => $depth);
714             $self->__setErrorTextBuffer ($message);
715             $self->__resetGUI;
716             return;
717             }
718              
719             $#indices = $depth;
720             $indices[$depth] = defined $indices[$depth] ? $indices[$depth] + 1 : 0;
721              
722             my $hpath_str = join ':', @indices;
723             my $hpath = Gtk2::TreePath->new_from_indices (@indices);
724              
725             my %args = (path => $hpath_str,
726             name => $name);
727              
728             $args{is_test} = 1 if '-' eq $type;
729              
730             my $node = Test::Unit::GTestRunner::Node->new (%args);
731             push @nodes, $node;
732              
733             $hpath->up;
734             my $parent = $depth ? $store->get_iter ($hpath) : undef;
735             my $iterator = $store->append ($parent);
736             $store->set ($iterator,
737             0 => $node->getName,
738             1 => $node->getStockID);
739              
740             unless ($dirty) {
741             my $new_path = $store->get_path ($iterator);
742             my $new_path_str = $new_path->to_string;
743             my $old_iter = $old_store->get_iter_from_string ($new_path_str);
744              
745             unless ($old_iter) {
746             $dirty = 1;
747             next;
748              
749             }
750              
751             my $old_name = $old_store->get ($old_iter, 0);
752             $dirty = !defined $old_name || $old_name ne $name;
753             }
754             }
755              
756             if ($dirty) {
757             $self->{__nodes} = \@nodes;
758             $self->{__hierarchy_view}->set_model ($store);
759             $self->{__hierarchy_store} = $store;
760              
761             $self->{__nodes_by_path} = {};
762             foreach my $node (@nodes) {
763             $self->{__nodes_by_path}->{$node->getPath} = $node;
764             }
765             }
766              
767             $$dirty_flag = $dirty if $dirty_flag;
768              
769             $self->__setGUIState ('loaded');
770              
771             return 1;
772             }
773              
774             sub __selectTestCase {
775             my ($self, $path_string) = @_;
776              
777             my $node = $self->{__nodes_by_path}->{$path_string};
778            
779             my $hierarchy_view = $self->{__hierarchy_view};
780             my $failure_view = $self->{__failure_view};
781              
782             my $hierarchy_paths = [$hierarchy_view->get_selection->get_selected_rows];
783             my $failure_paths = [$failure_view->get_selection->get_selected_rows];
784              
785             if ((@$hierarchy_paths || @$failure_paths) && !$self->{__pid}) {
786             $self->__setGUIState ('loaded_selected');
787             }
788              
789             unless ($node->isTest) {
790             # This is an inner node of the tree.
791             $self->__setErrorTextBuffer ('');
792              
793             my $tree_selection = $failure_view->get_selection;
794             $tree_selection->unselect_all;
795              
796             return 1;
797             }
798              
799             # This is a leaf, and we have a corresponding test case.
800             $self->__setErrorTextBuffer ($node->getResult, $node->getOutput);
801             my $hierarchy_path = Gtk2::TreePath->new_from_string ($path_string);
802              
803             if ($hierarchy_path) {
804             my ($old_path, undef) = $hierarchy_view->get_cursor;
805            
806             if (!defined $old_path || $old_path->compare ($hierarchy_path)) {
807             $hierarchy_view->expand_to_path ($hierarchy_path);
808             $hierarchy_view->scroll_to_cell ($hierarchy_path);
809             $hierarchy_view->get_selection->select_path ($hierarchy_path);
810             $hierarchy_view->set_cursor ($hierarchy_path);
811             }
812             }
813              
814             my $failure_index = $node->getFailurePath;
815              
816             if (defined $failure_index) {
817             my $failure_path = Gtk2::TreePath->new_from_string ($failure_index);
818              
819             if ($failure_path) {
820             my ($old_path, undef) = $failure_view->get_cursor;
821              
822             if (!defined $old_path || $old_path->compare ($failure_path)) {
823             $failure_view->expand_to_path ($failure_path);
824             $failure_view->scroll_to_cell ($failure_path);
825             $failure_view->get_selection->select_path ($failure_path);
826             $failure_view->set_cursor ($failure_path);
827             }
828             }
829             } else {
830             # Unselect.
831             my $tree_selection = $failure_view->get_selection;
832             $tree_selection->unselect_all;
833             }
834              
835             return 1;
836             }
837              
838             sub __setStatusBar {
839             my ($self, $msg) = @_;
840              
841             my $statusbar = $self->{__statusbar};
842             my $context_id = $self->{__context_id};
843              
844             $statusbar->pop ($context_id);
845              
846             $context_id = $self->{__context_id} =
847             $statusbar->get_context_id (__PACKAGE__);
848              
849             $statusbar->push ($context_id, ' ' . $msg);
850              
851             return 1;
852             }
853              
854             sub __onHierarchyChange {
855             my ($self, $view) = @_;
856              
857             my ($path, $focus_column) = $view->get_cursor;
858              
859             if ($path) {
860             my $str_path = $path->to_string;
861              
862             return $self->__selectTestCase ($str_path);
863             }
864              
865             $self->__setErrorTextBuffer ('');
866              
867             return 1;
868             }
869              
870             sub __onFailureChange {
871             my ($self, $view) = @_;
872              
873             my ($path, $focus_column) = $view->get_cursor;
874              
875             if ($path) {
876             my $index = $path->to_string;
877             my $node = $self->{__failures}->[0 + $index];
878              
879             unless ($self->{__pid}) {
880             $self->__setGUIState ('loaded_selected');
881             }
882              
883             return $self->__selectTestCase ($node->getPath);
884             }
885              
886             $self->__setErrorTextBuffer ('');
887              
888             return 1;
889             }
890              
891             sub __onHierarchyActivated {
892             my ($self, $view, $path) = @_;
893              
894             my $str_path = $path->to_string;
895              
896             if ($self->{__tests_by_path}->{$str_path}) {
897             my $record = $self->{__tests_by_path}->{$str_path};
898            
899             return 1 unless defined $record->{failure_path};
900              
901             # Currently selected or not?
902             my $selection = $self->{__hierarchy_view}->get_selection;
903             my $selected = $selection->path_is_selected ($path);
904             $self->__selectAllFailures ($selected);
905             } else {
906             $view->row_expanded ($path) ?
907             $view->collapse_row ($path) : $view->expand_row ($path, 1);
908             }
909              
910             return 1;
911             }
912              
913             sub __onSwitchPage {
914             my ($self, $notebook, undef, $current_page) = @_;
915              
916             return 1 unless @{$self->{__suites}};
917              
918             my $view = $current_page == 0 ?
919             $self->{__failure_view} : $self->{__hierarchy_view};
920              
921             my $selection = $view->get_selection;
922              
923             my $selected = $selection->count_selected_rows;
924              
925             unless ($self->{__pid}) {
926             if ($selected) {
927             $self->__setGUIState ('loaded_selected');
928             } else {
929             $self->__setGUIState ('loaded');
930             }
931             }
932            
933             return 1;
934             }
935              
936             sub __quitApplication {
937             Gtk2->main_quit;
938             }
939              
940             sub __showAboutDialog {
941             my ($self) = @_;
942              
943             my $viktor = "\x{412}\x{438}\x{43a}\x{442}\x{43e}\x{440} "
944             . "\x{41a}\x{43e}\x{436}\x{443}\x{445}\x{430}\x{440}\x{43e}\x{432} "
945             . "";
946              
947             my $main_window = $self->{__gladexml}->get_widget ('GTestRunner');
948             Gtk2->show_about_dialog ($main_window,
949             name => 'GTestRunner',
950             version => $VERSION,
951             authors => [ 'Guido Flohr ',
952             $viktor],
953             translator_credits =>
954             # TRANSLATORS: Replace this string with your
955             # own names and e-mail addresses, one name
956             # per line.
957             __"translator-credits"
958             );
959             }
960              
961             sub __showFileSelection {
962             my ($self) = @_;
963              
964             $self->{__new_file_chooser} = 1 if exists $ENV{GFC};
965              
966             if ($self->{__new_file_chooser}) {
967             my $dialog = Gtk2::FileChooserDialog->new (
968             __"Select a test suite or test case to run!",
969             undef,
970             'open',
971             'gtk-cancel' => 'GTK_RESPONSE_CANCEL',
972             'gtk-open' => 'GTK_RESPONSE_OK',
973             );
974              
975             $dialog->set_select_multiple (1);
976             $dialog->set_current_folder ($self->{__current_dir})
977             if $self->{__current_dir};
978            
979             my $result = $dialog->run;
980             $self->{__suites} = [$dialog->get_filenames] if 'ok' eq $result;
981             $self->{__current_dir} = $dialog->get_current_folder;
982             $dialog->destroy;
983             } else {
984             require File::Basename;
985             my $dialog = Gtk2::FileSelection->new (__("Select a test suite or " .
986             "test case to run!"));
987              
988             $dialog->set_select_multiple (1);
989             $dialog->set_filename ($self->{__current_dir})
990             if $self->{__current_dir};
991            
992             my $result = $dialog->run;
993            
994             $self->{__suites} = [$dialog->get_selections] if 'ok' eq $result;
995             $self->{__current_dir} =
996             File::Basename::dirname ($dialog->get_filename) . '/';
997             $dialog->destroy;
998             }
999             $self->__loadSuite if @{$self->{__suites}};
1000              
1001             return 1;
1002             }
1003              
1004             sub __handleReply {
1005             my ($self) = @_;
1006              
1007             my $rin = '';
1008             vec ($rin, $self->{__cmd_fileno}, 1) = 1;
1009              
1010             my $win = my $ein = '';
1011             my $nfound = select $rin, $win, $ein, 0;
1012             return $self->__terminateTests (__x ("Select on pipe to child process "
1013             . "failed: {err}.", err => $!))
1014             if $nfound < 0;
1015              
1016             return unless $nfound;
1017              
1018             my $num_bytes;
1019             my $bytes = sysread $self->{__cmd_fh}, $num_bytes, 9;
1020             return $self->__terminateTests (__("Unexpected end of file while reading "
1021             . "from child process.")) unless $bytes;
1022              
1023             return $self->__terminateTests (__x ("Read from pipe to child process "
1024             . "failed: {err}.", err => $!))
1025             if $bytes < 0;
1026              
1027             chop $num_bytes;
1028             $num_bytes = hex $num_bytes;
1029             return $self->__terminateTests (__("Unexpected end of file while reading "
1030             . "from child process."))
1031             if $bytes <= 0;
1032              
1033             my $reply = '';
1034             my $chunk;
1035             my $bytes_to_read = $num_bytes;
1036              
1037             while ($bytes_to_read > 0) {
1038             $bytes = sysread $self->{__cmd_fh}, $chunk, $bytes_to_read;
1039             return $self->__terminateTests (__("Unexpected end of file while "
1040             . "reading from child process."))
1041             unless $bytes;
1042             return $self->__terminateTests (__x ("Read from pipe to child process "
1043             . "failed: {err}.", err => $!))
1044             if $bytes < 0;
1045              
1046             $bytes_to_read -= $bytes;
1047              
1048             chop $chunk;
1049             $reply .= $chunk;
1050             }
1051              
1052             warn "<<< REPLY: $reply\n" if DEBUG;
1053              
1054             my ($cmd, $args) = split / +/, $reply, 2;
1055              
1056             my $method = '__handleReply' . ucfirst $cmd;
1057              
1058             warn "+++ REPLY: $reply\n" if DEBUG;
1059             $self->$method ($args);
1060              
1061             return 1;
1062             }
1063              
1064             sub __handleReplyPrint {
1065             my ($self, $message) = @_;
1066              
1067             my $cleartext = decode_base64 $message;
1068              
1069             my $index = $self->{__counter};
1070              
1071             my $node = $self->{__planned}->[$index];
1072              
1073             $node->addOutput ($cleartext);
1074              
1075             $self->__setErrorTextBuffer ($node->getResult, $node->getOutput);
1076              
1077             # We have been here before.
1078             return 1 if defined $node->getFailurePath;
1079              
1080             my $hpath_str = $node->getPath;
1081             $self->__selectTestCase ($hpath_str);
1082              
1083             my $hstore = $self->{__hierarchy_store};
1084             my $iterator = $hstore->get_iter_from_string ($hpath_str);
1085              
1086             my $test = $hstore->get ($iterator, 0);
1087             $hstore->set ($iterator, 1 => 'gtk-dialog-warning');
1088              
1089             my $package = '';
1090             my $hpath = Gtk2::TreePath->new_from_string ($hpath_str);
1091             if ($hpath->up) {
1092             $iterator = $hstore->get_iter ($hpath);
1093             $package = $hstore->get ($iterator, 0);
1094             }
1095              
1096             my $fstore = $self->{__failure_store};
1097              
1098             $iterator = $fstore->append;
1099             $fstore->set ($iterator,
1100             0 => 'gtk-dialog-warning',
1101             1 => $test,
1102             2 => $package);
1103              
1104             my $fpath = $fstore->get_path ($iterator);
1105              
1106             $node->setFailurePath ($fpath->to_string);
1107              
1108             push @{$self->{__failures}}, $node;
1109              
1110             $self->__selectTestCase ($node->getPath);
1111            
1112             return 1;
1113             }
1114              
1115             sub __handleReplyPid {
1116             my ($self, $pid) = @_;
1117              
1118             $self->{__pid} = $pid;
1119              
1120             return 1;
1121             }
1122              
1123             sub __resetGUI {
1124             my $self = shift;
1125              
1126             my $gladexml = $self->{__gladexml};
1127              
1128             if (@{$self->{__suites}}) {
1129             my $notebook = $gladexml->get_widget ('notebook');
1130             my $current_page = $notebook->get_current_page;
1131             my $view = $current_page == 0 ?
1132             $self->{__failure_view} : $self->{__hierarchy_view};
1133             my $selected = $view->get_selection->count_selected_rows;
1134              
1135             my $state = $selected ? 'loaded_selected' : 'loaded';
1136              
1137             $self->__setGUIState ($state);
1138              
1139             } else {
1140             $self->__setGUIState ('initial');
1141             }
1142            
1143             Glib::Source->remove ($self->{__timeout_id}) if $self->{__timeout_id};
1144             $self->{__pid} = 0;
1145             undef $self->{__last_signal};
1146              
1147             return 1;
1148             }
1149              
1150             sub __handleReplyTerminated {
1151             my $self = shift;
1152             $self->__resetGUI;
1153             $self->__setStatusBar (__"Test terminated.");
1154              
1155             return 1;
1156             }
1157              
1158             sub __handleReplyStart {
1159             my ($self, $test) = @_;
1160              
1161             $self->__setStatusBar (__x"Running: {test}", test => $test);
1162              
1163             my $num_tests = $self->{__counter};
1164              
1165             my $num_errors = $self->{__error_count};
1166             my $num_failures = $self->{__failure_count} - $num_errors;
1167             my $message = __nx ("one test, ", "{num_tests} tests, ", $num_tests,
1168             num_tests => $num_tests);
1169             $message .= __nx ("one error, ", "{num_errors} errors, ", $num_errors,
1170             num_errors => $num_errors);
1171             $message .= __nx ("one failure", "{num_failures} failures", $num_failures,
1172             num_failures => $num_failures);
1173              
1174             $self->{__progress_bar}->set_text ($message);
1175              
1176             my $index = $num_tests;
1177             my $node = $self->{__planned}->[$index];
1178              
1179             my $store = $self->{__hierarchy_store};
1180              
1181             my $hpath = Gtk2::TreePath->new_from_string ($node->getPath);
1182             my $iterator = $store->get_iter ($hpath);
1183              
1184             my $icon = $store->get ($iterator, 1);
1185             $store->set ($iterator, 1 => 'gtk-apply');
1186              
1187             return 1;
1188             }
1189              
1190             sub __handleReplyEnd {
1191             my ($self, $test) = @_;
1192              
1193             ++$self->{__counter};
1194              
1195             my $num_tests = $self->{__counter};
1196             my $fraction = $self->{__num_planned} ?
1197             ($num_tests / $self->{__num_planned}) : 1;
1198             $self->{__progress_bar}->set_fraction ($fraction);
1199              
1200             my $num_errors = $self->{__error_count};
1201             my $num_failures = $self->{__failure_count} - $num_errors;
1202             my $message = __nx ("one test, ", "{num_tests} tests, ", $num_tests,
1203             num_tests => $num_tests);
1204             $message .= __nx ("one error, ", "{num_errors} errors, ", $num_errors,
1205             num_errors => $num_errors);
1206             $message .= __nx ("one failure", "{num_failures} failures", $num_failures,
1207             num_failures => $num_failures);
1208              
1209             $self->{__progress_bar}->set_text ($message);
1210              
1211             if ($num_failures == 0 && $num_errors == 0) {
1212             $self->{__progress_bar}->modify_bg ('normal', $self->{__green});
1213             $self->{__progress_image}->set_from_stock ('gtk-apply', 'button');
1214             }
1215              
1216             return 1;
1217             }
1218              
1219             sub __handleReplySuccess {
1220             my ($self, $reply) = @_;
1221              
1222             my ($test) = split / +/, $reply, 1;
1223              
1224             $self->__setStatusBar (__x"Success: {test}", test => $test);
1225              
1226             my $index = $self->{__counter};
1227              
1228             my $node = $self->{__planned}->[$index];
1229              
1230             my $store = $self->{__hierarchy_store};
1231              
1232             my $hpath = Gtk2::TreePath->new_from_string ($node->getPath);
1233             my $iterator = $store->get_iter ($hpath);
1234              
1235             my $icon = $store->get ($iterator, 1);
1236             $store->set ($iterator, 1 => 'gtk-apply')
1237             unless $icon eq 'gtk-dialog-warning';
1238              
1239             return 1;
1240             }
1241              
1242             sub __handleReplyFailure {
1243             my ($self, $reply) = @_;
1244              
1245             ++$self->{__failure_count};
1246              
1247             my ($test, $obj) = split / +/, $reply, 2;
1248              
1249             $self->__setStatusBar (__x"Failure: {test}", test => $test);
1250              
1251             my $failure = thaw decode_base64 $obj;
1252              
1253             my $package = $failure->{package};
1254             my $file = $failure->{file};
1255             my $line = $failure->{line};
1256             my $text = $failure->{text};
1257              
1258             my $index = $self->{__counter};
1259             my $node = $self->{__planned}->[$index];
1260             $node->setFailure;
1261              
1262             my $failure_store = $self->{__failure_store};
1263              
1264             my $iterator;
1265             if (defined $node->getFailurePath) {
1266             my $fpath = Gtk2::TreePath->new_from_string ($node->getFailurePath);
1267             $iterator = $failure_store->get_iter ($fpath);
1268             } else {
1269             $iterator = $failure_store->append;
1270             my $fpath = $failure_store->get_path ($iterator);
1271             $node->setFailurePath ($fpath->to_string);
1272            
1273             push @{$self->{__failures}}, $node;
1274             }
1275              
1276             $failure_store->set ($iterator,
1277             0 => $node->getStockID,
1278             1 => $test,
1279             2 => $package,
1280             3 => "$file:$line");
1281              
1282             $node->setResult ($text);
1283              
1284             $self->__setErrorTextBuffer ($text, $node->getOutput);
1285              
1286             $self->{__progress_image}->set_from_stock ($node->getStockID,
1287             'button');
1288             $self->{__progress_bar}->modify_bg ('normal', $self->{__red});
1289              
1290             my $store = $self->{__hierarchy_store};
1291              
1292             my $hpath = Gtk2::TreePath->new_from_string ($node->getPath);
1293             $iterator = $store->get_iter ($hpath);
1294             $store->set ($iterator, 1 => $node->getStockID);
1295              
1296             $self->__selectTestCase ($node->getPath);
1297            
1298             return 1;
1299             }
1300              
1301             sub __handleReplyError {
1302             my ($self, $reply) = @_;
1303              
1304             ++$self->{__error_count};
1305              
1306             my $node = $self->{__planned}->[$self->{__counter}];
1307             $node->setError;
1308              
1309             $self->__handleReplyFailure ($reply);
1310             }
1311              
1312             # FIXME! What should happen here?
1313             sub __handleReplyWarning {
1314             my ($self, $warning) = @_;
1315              
1316             warn "$warning\n";
1317              
1318             return 1;
1319             }
1320              
1321             sub __setErrorTextBuffer {
1322             my ($self, $text, $output) = @_;
1323              
1324             my $buffer = $self->{__error_textbuffer};
1325            
1326             $text = '' unless defined $text;
1327              
1328             $buffer->set_text ($text);
1329              
1330             my ($start, $end) = $buffer->get_bounds;
1331            
1332             $buffer->apply_tag_by_name ('error', $start, $end);
1333             $buffer->insert ($start, $output) if defined $output;
1334              
1335             return 1;
1336             }
1337              
1338             sub __handleReplyAbort {
1339             my ($self, $message) = @_;
1340              
1341             $self->__setErrorTextBuffer ($message);
1342             $self->__handleReplyTerminated;
1343             $self->__setStatusBar (__"Test aborted.");
1344              
1345             return 1;
1346             }
1347              
1348             sub __sendKill {
1349             my ($self) = @_;
1350              
1351             Glib::Source->remove ($self->{__timeout_id}) if $self->{__timeout_id};
1352             return 1 unless $self->{__pid};
1353              
1354             # Still alive?
1355             my $alive = kill 0 => $self->{__pid};
1356             unless ($alive) {
1357             $self->__resetGUI;
1358             $self->__setStatusBar (__"Test process terminated.");
1359             return 1;
1360             }
1361              
1362             $self->{__last_signal} = -1 unless defined $self->{__last_signal};
1363            
1364             ++$self->{__last_signal};
1365              
1366             unless (defined $self->{__kill_signals}->[$self->{__last_signal}]) {
1367             $self->__resetGUI;
1368             $self->__setStatusBar
1369             (__"Child process cannot be terminated.");
1370             return 1;
1371             }
1372              
1373             my ($signame, $signo) =
1374             @{$self->{__kill_signals}->[$self->{__last_signal}]};
1375              
1376             $self->__setStatusBar (__x ("Child process signaled with SIG{NAME}.",
1377             NAME => $signame));
1378             kill $signo => $self->{__pid};
1379            
1380             $self->{__timeout_id} = Glib::Timeout->add (1500, sub {
1381             $self->__sendKill;
1382             return 1;
1383             });
1384              
1385             return 1;
1386             }
1387              
1388             sub __setGUIState {
1389             my ($self, $state) = @_;
1390            
1391             my $record = GUI_STATES->{$state};
1392              
1393             unless ($record) {
1394             my $message = __x (< $state);
1395             Internal error: Unrecognized error state "{state}". This should
1396             not happen.
1397             EOF
1398              
1399             my $main_window = $self->{__gladexml}->get_widget ('GTestRunner');
1400             my $dialog = Gtk2::MessageDialog->new ($main_window,
1401             'destroy-with-parent',
1402             'error',
1403             'ok',
1404             $message,
1405             );
1406             $dialog->run;
1407             Gtk2->main_quit;
1408             exit 1;
1409             }
1410              
1411             $self->{__gui_state} = $state;
1412              
1413             my $gladexml = $self->{__gladexml};
1414             while (my ($key, $value) = each %$record) {
1415             $gladexml->get_widget ($key)->set_sensitive ($value);
1416             }
1417              
1418             return 1;
1419             }
1420              
1421             sub __selectAllFailures {
1422             my ($self) = @_;
1423              
1424             my $hstore = $self->{__hierarchy_store};
1425             my $hview = $self->{__hierarchy_view};
1426             my $hselection = $hview->get_selection;
1427             $hselection->unselect_all;
1428              
1429             my $fstore = $self->{__failure_store};
1430             my $fview = $self->{__failure_view};
1431             my $fselection = $fview->get_selection;
1432             $fselection->unselect_all;
1433            
1434             my $records = $self->{__tests_by_path};
1435             foreach my $str_hpath (keys %$records) {
1436             my $str_fpath = $records->{$str_hpath}->{failure_path};
1437             next unless defined $str_fpath;
1438              
1439             my $hpath = Gtk2::TreePath->new_from_string ($str_hpath);
1440             $hselection->select_path ($hpath);
1441              
1442             my $fpath = Gtk2::TreePath->new_from_string ($str_fpath);
1443             $fselection->select_path ($fpath);
1444             }
1445              
1446             if (!$self->{__pid}) {
1447             my $new_state = 'loaded';
1448             $new_state .= '_selected' if $hselection->count_selected_rows;
1449             }
1450             }
1451              
1452             1;
1453              
1454             =head1 NAME
1455              
1456             Test::Unit::GTestRunner - Unit testing framework helper class
1457              
1458             =head1 SYNOPSIS
1459              
1460             use Test::Unit::GTestRunner;
1461              
1462             Test::Unit::GTestRunner->new->start ($my_testcase_class);
1463              
1464             Test::Unit::GTestRunner::main ($my_testcase_class);
1465              
1466             =head1 DESCRIPTION
1467              
1468             If you just want to run a unit test (suite), try it like this:
1469              
1470             gtestrunner "MyTestSuite.pm"
1471              
1472             Try "perldoc gtestrunner" or "man gtestrunner" for more information.
1473              
1474             This class is a GUI test runner using the Gimp Toolkit Gtk+ (which
1475             is called Gtk2 in Perl). You can use it if you want to integrate
1476             the testing framework into your own application.
1477              
1478             For a description of the graphical user interface, please see
1479             gtestrunner(1).
1480              
1481             =head1 EXAMPLE
1482              
1483             You will usually invoke it from a runner script like this:
1484              
1485             #! /usr/local/bin/perl -w
1486              
1487             use strict;
1488            
1489             require Test::Unit::GTestRunner;
1490              
1491             Test::Unit::GTestRunner::main (@ARGV) or exit 1;
1492              
1493             See Test::Unit::TestRunner (3) for details.
1494              
1495             An internationalized version would go like this:
1496              
1497             #!/usr/bin/perl -w
1498              
1499             use strict;
1500              
1501             use Test::Unit::GTestRunner;
1502             use POSIX;
1503             use Locale::Messages qw (LC_ALL);
1504              
1505             POSIX::setlocale (LC_ALL, "");
1506              
1507             Test::Unit::GTestRunner::main (@ARGV) or exit (1);
1508              
1509             =head1 CONSTRUCTOR
1510              
1511             =over 4
1512              
1513             =item B
1514              
1515             The constructor takes no arguments. It will throw an exception in
1516             case of failure.
1517              
1518             =back
1519              
1520             =head1 METHODS
1521              
1522             =over 4
1523              
1524             =item B
1525              
1526             The method fires up the graphical user interface and will never
1527             return.
1528              
1529             The optional arguments B can either be the name of a file
1530             containing a test suite (see Test::Unit::TestSuite(3pm)), for
1531             example "TS_MySuite.pm", or the name of a Perl module, for example
1532             "Tests::TS_MySuite". Multiple suites passed as arguments to
1533             the method are assembled into one virtual top-level suite that is
1534             hidden from the display.
1535              
1536             =back
1537              
1538             =head1 FUNCTIONS
1539              
1540             =over 4
1541              
1542             =item B
1543              
1544             If you prefer a functional interface, you can also start a test
1545             session with
1546              
1547             Test::Unit::GTestRunner::main ($suite_name);
1548              
1549             The optional argument B is interpreted as described above
1550             for the method start().
1551              
1552             =back
1553              
1554             =head1 AUTHOR
1555              
1556             Copyright (C) 2004-2006, Guido Flohr Eguido@imperia.netE, all
1557             rights reserved. See the source code for details.
1558              
1559             This software is contributed to the Perl community by Imperia
1560             (L).
1561              
1562             =head1 ENVIRONMENT
1563              
1564             The package is internationalized with libintl-perl, hence the
1565             environment variables "LANGUAGE", "LANG", "LC_MESSAGES", and
1566             "LC_ALL" will influence the language in which the GUI and
1567             messages are presented.
1568              
1569             =head1 SEE ALSO
1570              
1571             gtestrunner(1), Test::Unit::TestRunner(3pm), Test::Unit(3pm),
1572             Locale::Messages(3pm), perl(1)
1573              
1574             =cut
1575              
1576             #Local Variables:
1577             #mode: perl
1578             #perl-indent-level: 4
1579             #perl-continued-statement-offset: 4
1580             #perl-continued-brace-offset: 0
1581             #perl-brace-offset: -4
1582             #perl-brace-imaginary-offset: 0
1583             #perl-label-offset: -4
1584             #cperl-indent-level: 4
1585             #cperl-continued-statement-offset: 2
1586             #tab-width: 4
1587             #End:
1588              
1589             __DATA__