File Coverage

blib/lib/Jifty/Test.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1 107     107   857029 use warnings;
  107         380  
  107         6160  
2 107     107   982 use strict;
  107         237  
  107         5005  
3              
4             package Jifty::Test;
5 107     107   1827 use base qw/Test::More/;
  107         252  
  107         148728  
6              
7 107     107   2401169 use Jifty::Util;
  0            
  0            
8             use Email::LocalDelivery;
9             use Email::Folder;
10             use File::Path;
11             use File::Spec;
12             use File::Temp;
13             use Hash::Merge;
14             use Digest::MD5 qw/md5_hex/;
15             use Cwd qw/abs_path cwd/;
16              
17             # Mechanize uses Test::LongString to report mismatches. Increase the
18             # limit so we can see where errors come from more easily.
19             use Test::LongString;
20             $Test::LongString::Max = 128;
21              
22             =head1 NAME
23              
24             Jifty::Test - Jifty's test module
25              
26             =head1 SYNOPSIS
27              
28             use Jifty::Test tests => 5;
29             # to load po for test:
30             # use Jifty::Test tests => 5, l10n => 1;
31              
32             # ...all of Test::More's functionality...
33             my $model = MyApp::Model::MyModel->new;
34             $model->create();
35             ok($model->id, 'model works');
36             is($model->foo, 'some default', 'default works');
37              
38             # Startup an external server (see Jifty::TestServer)
39             my $server = Jifty::Test->make_server;
40             my $server_url = $server->started_ok;
41             # You're probably also interested in Jifty::Test::WWW::Mechanize
42              
43              
44             =head1 DESCRIPTION
45              
46             Jifty::Test is a superset of L. It provides all of
47             Test::More's functionality in addition to the class methods defined
48             below.
49              
50             =head1 METHODS
51              
52             =head2 is_passing
53              
54             my $is_passing = Jifty::Test->is_passing;
55              
56             Check if the test is currently in a passing state.
57              
58             =over
59              
60             =item *
61              
62             All tests run so far have passed
63              
64             =item *
65              
66             We have run at least one test
67              
68             =item *
69              
70             We have not run more than we planned (if we planned at all)
71              
72             =back
73              
74             =cut
75              
76             sub is_passing {
77             my $tb = Jifty::Test->builder;
78              
79             my $is_failing = grep {not $_} $tb->summary;
80             no warnings 'uninitialized';
81             $is_failing ||= $tb->has_plan eq 'no_plan'
82             ? 0
83             : $tb->expected_tests < $tb->current_test;
84              
85             return !$is_failing;
86             }
87              
88              
89             =head2 is_done
90              
91             my $is_done = Jifty::Test->is_done;
92              
93             Check if we have run all the tests we've planned.
94              
95             If the plan is 'no_plan' then is_done() will return true if at least
96             one test has run.
97              
98             =cut
99              
100             sub is_done {
101             my $tb = Jifty::Test->builder;
102              
103             no warnings 'uninitialized';
104             if( $tb->has_plan eq 'no_plan' ) {
105             return $tb->current_test > 0;
106             }
107             else {
108             return $tb->expected_tests == $tb->current_test;
109             }
110             }
111              
112              
113             =begin private
114              
115             =head2 import_extra
116              
117             Called by L's C code when L is first
118             C'd, it calls L, and asks Test::More to export its
119             symbols to the namespace that C'd this one.
120              
121             =end private
122              
123             =cut
124              
125             our $imported = 0;
126              
127             sub import_extra {
128             my $class = shift;
129             my $args = shift;
130              
131             $imported = 1;
132              
133             $class->setup($args);
134             Test::More->export_to_level(2);
135              
136             # Now, clobber Test::Builder::plan (if we were given a plan) so
137             # Test::Builder::Module doesn't plan for us
138             if ($class->builder->has_plan) {
139             no warnings 'redefine';
140             *Test::Builder::plan = sub {};
141             }
142              
143             # the modified $args is then passed to Test::Builder's plan. we should
144             # strip our custom items.
145             # XXX: this should probably be done in _strip_imports
146             # we check for multiple args because of 'no_plan'
147             if (@$args > 1) {
148             my %args = @$args;
149             @$args = map { $args{$_} ? ($_ => $args{$_ }) : () } qw(tests skip_all);
150             }
151             }
152              
153             =head2 setup ARGS
154              
155             This method is passed a single argument. This is a reference to the array of parameters passed in to the import statement.
156              
157             Merges the L into the default configuration, resets the
158             database, and resets the fake "outgoing mail" folder.
159              
160             This is the method to override if you wish to do custom setup work, such as
161             insert test data into your database.
162              
163             package MyApp::Test;
164             use base qw/ Jifty::Test /;
165              
166             sub setup {
167             my $self = shift;
168             my $args = shift;
169              
170             # Make sure to call the super-class version
171             $self->SUPER::setup($args);
172              
173             # Now that we have the database and such...
174             my %test_args = @$args;
175              
176             if ($test_arg{something_special}) {
177             # do something special...
178             }
179             }
180              
181             And later in your tests, you may do the following:
182              
183             use MyApp::Test tests => 14, something_special => 1;
184              
185             # 14 tests with some special setup...
186              
187             =cut
188              
189             my $WARNINGS_ARE_FATAL;
190              
191             sub setup {
192             my $class = shift;
193             my $args = shift;
194              
195             $args ||= [];
196             my %args = @{$args} % 2 ? (@{$args}, 1) : @{$args};
197              
198             my $root = Jifty::Util->app_root;
199              
200             require Jifty::YAML;
201             # naive detect of configfileversion before jifty->new, since you
202             # probably don't want to override it in site_config or other places.
203             my $config = eval { Jifty::YAML::LoadFile("$root/etc/config.yml") };
204             if ($config && $config->{framework}{ConfigFileVersion} &&
205             $config->{framework}{ConfigFileVersion} < 5) {
206             $ENV{JIFTY_TEST_SERVER} ||= 'Standalone';
207             }
208              
209             my $server = $ENV{JIFTY_TEST_SERVER} ||=
210             $args{actual_server} ? 'Standalone' : 'Inline';
211              
212             if ($server eq 'Inline') {
213             require Jifty::Test::WWW::Mechanize;
214             require Test::WWW::Mechanize::PSGI;
215             unshift @Jifty::Test::WWW::Mechanize::ISA, 'Test::WWW::Mechanize::PSGI';
216             }
217              
218             if ($args{actual_server}) {
219             $class->builder->plan(skip_all => "This test requires an actual test server to run. Run with JIFTY_TEST_SERVER=Standalone instead")
220             if $ENV{JIFTY_TEST_SERVER} eq 'Inline';
221             }
222              
223             # Spit out a plan (if we got one) *before* we load modules, in
224             # case of compilation errors
225             unless ($class->builder->has_plan) {
226             $class->builder->plan(map { $_ => $args{$_ } } qw(tests skip_all))
227             if $args{tests} || $args{skip_all};
228             }
229              
230             # Require the things we need
231             require Jifty::Script::Schema;
232              
233             $class->builder->{no_handle} = $args{no_handle};
234             $WARNINGS_ARE_FATAL = 1 if $args{strict};
235             my $test_config = File::Temp->new( UNLINK => 0 );
236             Jifty::YAML::DumpFile("$test_config", $class->test_config(Jifty::Config->new, \%args));
237             # Invoking bin/jifty and friends will now have the test config ready.
238             $ENV{'JIFTY_TEST_CONFIG'} ||= "$test_config";
239             $class->builder->{test_config} = $test_config;
240             {
241             # Cache::Memcached stores things. And doesn't let them expire
242             # from the cache easily. This is fine in production, but
243             # during testing each test script needs its own namespace. we
244             # use the pid of the current process, and save it so the keys
245             # stays the same when we fork
246             {
247             package Jifty::Record;
248             no warnings qw/redefine/;
249              
250             use vars qw/$cache_key_prefix/;
251              
252             $cache_key_prefix = "jifty-test-" . $$;
253            
254             *Jifty::Record::cache_key_prefix = sub {
255             $Jifty::Record::cache_key_prefix;
256             }
257             }
258            
259             }
260             # Mason's disk caching sometimes causes false tests
261             rmtree([ File::Spec->canonpath("$root/var/mason") ], 0, 1);
262              
263             $class->setup_test_database;
264              
265             $class->setup_mailbox;
266             }
267              
268             =head2 setup_test_database
269              
270             Create the test database. This can be overloaded if you do your databases in a
271             different way.
272              
273             =cut
274              
275             sub setup_test_database {
276             my $class = shift;
277              
278             if ($class->builder->{no_handle}) {
279             Jifty->new( no_handle => 1 );
280             return;
281             }
282              
283             if ($ENV{JIFTY_FAST_TEST}) {
284             local $SIG{__WARN__} = sub {};
285             eval { Jifty->new( no_version_check => 1 ); Jifty->handle->check_schema_version };
286             my $booted;
287             if (Jifty->handle && !$@) {
288             my $baseclass = Jifty->app_class;
289             for my $model_class ( grep {/^\Q$baseclass\E::Model::/} Jifty::Schema->new->models ) {
290             # We don't want to get the Collections, for example.
291             next unless $model_class->isa('Jifty::DBI::Record');
292             Jifty->handle->simple_query('TRUNCATE '.$model_class->table );
293             Jifty->handle->simple_query('ALTER SEQUENCE '.$model_class->table.'_id_seq RESTART 1');
294             }
295             # Load initial data
296             eval {
297             my $bootstrapper = Jifty->app_class("Bootstrap");
298             Jifty::Util->require($bootstrapper);
299             $bootstrapper->run() if $bootstrapper->can('run');
300             };
301             die $@ if $@;
302             $booted = 1;
303             }
304             if (Jifty->handle) {
305             Jifty->handle->disconnect;
306             Jifty->handle(undef);
307             }
308             if ($booted) {
309             Jifty->new();
310             return;
311             }
312             }
313              
314             Jifty->new( no_handle => 1, pre_init => 1 );
315              
316             my $schema = Jifty::Script::Schema->new;
317             $schema->{drop_database} = 1;
318             $schema->{setup_tables} = 1;
319             $schema->run;
320              
321             Jifty->new();
322             }
323              
324             =head2 load_test_configs FILENAME
325              
326             This will load all the test config files that apply to FILENAME (default:
327             C<$0>, the current test script file). Say you are running the test script
328             C. The files that will be loaded are:
329              
330             =over 4
331              
332             =item C
333              
334             =item C
335              
336             =item C
337              
338             =back
339              
340             ..followed by the usual Jifty configuration files (such as
341             C and C). The options in a
342             more specific test file override the options in a less specific test file.
343              
344             The options are returned in a single hashref.
345              
346             =cut
347              
348             sub load_test_configs {
349             my $class = shift;
350             my ($test_config_file) = @_;
351              
352             # Jifty::Test::Dist uses chdir which screws up $0, so to be nice
353             # it also makes available the cwd was before it uses chdir.
354             my $cwd = $Jifty::Test::Dist::OrigCwd;
355              
356             # get the initial test config file, which is the input . "-config.yml"
357             $test_config_file = $0 if !defined($test_config_file);
358             $test_config_file .= "-config.yml";
359             $test_config_file = File::Spec->rel2abs($test_config_file, $cwd);
360              
361             my $test_options = _read_and_merge_config_file($test_config_file, {});
362              
363             # get the directory of the input, so we can recurse upwards
364             my ($volume, $directories) = File::Spec->splitpath($test_config_file);
365             my $directory = File::Spec->catdir($volume, $directories);
366              
367             my $depth = $ENV{JIFTY_TEST_DEPTH} || 30;
368              
369             for (1 .. $depth)
370             {
371             my $file = File::Spec->catfile($directory, "test_config.yml");
372             $test_options = _read_and_merge_config_file($file, $test_options);
373              
374             # are we at the app root? if so, then we can stop moving up
375             # did abs_path return undef? if so, there's not much we can do from here
376             $directory = abs_path(File::Spec->catdir($directory, File::Spec->updir($directory)));
377             return $test_options
378             if not defined $directory
379             or Jifty::Util->is_app_root($directory);
380             }
381              
382             Jifty->log->fatal("Stopping looking for test config files after recursing upwards $depth times. Either you have a nonstandard layout or an incredibly deep test hierarchy. If you really do have an incredibly deep test hierarchy, you can set the environment variable JIFTY_TEST_DEPTH to a larger value.") if (Jifty->logger);
383              
384             return $test_options;
385             }
386              
387             sub _read_and_merge_config_file {
388             my $file = shift;
389             my $config = shift;
390              
391             my $file_options = Jifty::Config->load_file($file);
392              
393             Hash::Merge::set_behavior('RIGHT_PRECEDENT');
394              
395             # merge the new options into what we have so far
396             return Hash::Merge::merge($file_options, $config);
397             }
398              
399             =head2 test_config
400              
401             Returns a hash which overrides parts of the application's
402             configuration for testing. By default, this changes the database name
403             by appending a 'test', as well as setting the port to a random port
404             between 10000 and 15000. Individual test configurations may override these
405             defaults (see C).
406              
407             It is passed the current configuration before any test config is loaded.
408              
409             You can override this to provide application-specific test
410             configuration, e.g:
411              
412             sub test_config {
413             my $class = shift;
414             my ($config) = @_;
415             my $hash = $class->SUPER::test_config($config);
416             $hash->{framework}{LogConfig} = "etc/log-test.conf"
417            
418             return $hash;
419             }
420              
421             Note that this is deprecated in favor of having real config files in your
422             test directory.
423              
424             =cut
425              
426             sub test_config {
427             my $class = shift;
428             my ($config, $args) = @_;
429              
430             my $defaults = {
431             framework => {
432             Database => {
433             Database => $config->framework('Database')->{Database} . $class->_testfile_to_dbname(),
434             },
435             L10N => {
436             Disable => $args->{l10n} ? 0 : 1,
437             },
438             Web => {
439             Port => ($$ % 5000) + 10000,
440             DataDir => File::Temp::tempdir('masonXXXXXXXXXX', CLEANUP => 1)
441             },
442             Plugins => [
443             { TestServerWarnings => {} },
444             ],
445             Mailer => 'Jifty::Test',
446             MailerArgs => [],
447             LogLevel => $ENV{JIFTY_TEST_LOGLEVEL} || 'FATAL',
448             TestMode => 1,
449             }
450             };
451              
452             if ($INC{'Devel/Cover.pm'}) {
453             $defaults->{framework}{DevelMode} = 0;
454             $defaults->{framework}{Web}{MasonConfig}{named_component_subs} = 1;
455             $defaults->{framework}{Web}{DataDir} = Jifty::Util->absolute_path( 'var/mason-cover' );
456             }
457              
458             Hash::Merge::set_behavior('RIGHT_PRECEDENT');
459             return Hash::Merge::merge($defaults, $class->load_test_configs);
460             }
461              
462              
463             sub _testfile_to_dbname {
464             return 'fasttest' if $ENV{JIFTY_FAST_TEST};
465             my $dbname = lc($0);
466             $dbname =~ s/\.t$//;
467             $dbname =~ s/(\W|[_-])+//g;
468             $dbname .= substr(md5_hex(cwd()), 0, 8);
469             $dbname = substr($dbname,-32,32);
470             return $dbname;
471             }
472              
473             =head2 make_server
474              
475             Creates a new L depending on the value of
476             C<$ENV{JIFTY_TEST_SERVER}>. If the environment variable is C,
477             we run tests using PSGI inline without spawning an actual server.
478             Otherwise, we fork off a L to run tests against.
479              
480             =cut
481              
482             sub make_server {
483             my $class = shift;
484             use Jifty::TestServer;
485              
486             my $server_class = $ENV{JIFTY_TEST_SERVER} eq 'Inline'
487             ? 'Jifty::TestServer::Inline' : 'Jifty::TestServer';
488             Jifty::Util->require($server_class) or die $!;
489              
490             $Jifty::SERVER = $server_class->new;
491             }
492              
493             =head2 web
494              
495             Like calling C<web>>.
496              
497             C<web>> does the necessary Jifty->web initialization for
498             it to be usable in a test.
499              
500             =cut
501              
502             sub web {
503             my $class = shift;
504              
505             Jifty->web->request(Jifty::Request->new) unless Jifty->web->request;
506             Jifty->web->response(Jifty::Response->new) unless Jifty->web->response;
507              
508             return Jifty->web;
509             }
510              
511              
512             =head2 mailbox
513              
514             A mailbox used for testing mail sending.
515              
516             =cut
517              
518             sub mailbox {
519             return Jifty::Util->absolute_path("t/mailbox_" . _testfile_to_dbname());
520             }
521              
522             =head2 setup_mailbox
523              
524             Clears the mailbox.
525              
526             =cut
527              
528             sub setup_mailbox {
529             my $class = shift;
530              
531             open my $f, ">:encoding(UTF-8)", $class->mailbox;
532             close $f;
533             }
534              
535             =head2 teardown_mailbox
536              
537             Deletes the mailbox.
538              
539             =cut
540              
541             sub teardown_mailbox {
542             unlink mailbox();
543             }
544              
545             =head2 is_available
546              
547             Informs L that L is always available as a mailer.
548              
549             =cut
550              
551             sub is_available { 1 }
552              
553             =head2 send
554              
555             Should not be called manually, but is
556             automatically called by L when using L as a mailer.
557              
558             (Note that it is a class method.)
559              
560             =cut
561              
562             sub send {
563             my $class = shift;
564             my $message = shift;
565              
566             Email::LocalDelivery->deliver($message->as_string, mailbox());
567             }
568              
569             =head2 messages
570              
571             Returns the messages in the test mailbox, as a list of
572             L objects. You may have to use a module like
573             L to parse multi-part messages stored in the mailbox.
574              
575             =cut
576              
577             sub messages {
578             return () unless -f mailbox();
579             return Email::Folder->new(mailbox())->messages;
580             }
581              
582              
583             =head2 test_file
584              
585             my $files = Jifty::Test->test_file($file);
586              
587             Register $file as having been created by the test. It will be
588             cleaned up at the end of the test run I the test
589             passes. Otherwise it will be left alone.
590              
591             It returns $file so you can do this:
592              
593             my $file = Jifty::Test->test_file( Jifty::Util->absolute_path("t/foo") );
594              
595             =cut
596              
597             my @Test_Files_To_Cleanup;
598             sub test_file {
599             my $class = shift;
600             my $file = shift;
601              
602             push @Test_Files_To_Cleanup, $file;
603              
604             return $file;
605             }
606              
607              
608             =head2 test_in_isolation
609              
610             my $return = Jifty::Test->test_in_isolation( sub {
611             ...your testing code...
612             });
613              
614             For testing testing modules so you can run testing code (which perhaps
615             fail) without effecting the outer test.
616              
617             Saves the state of Jifty::Test's Test::Builder object and redirects
618             all output to dev null before running your testing code. It then
619             restores the Test::Builder object back to its original state.
620              
621             # Test that fail() returns 0
622             ok !Jifty::Test->test_in_isolation sub {
623             return fail;
624             };
625              
626             =cut
627              
628             sub test_in_isolation {
629             my $class = shift;
630             my $code = shift;
631              
632             my $tb = Jifty::Test->builder;
633              
634             my $output = $tb->output;
635             my $failure_output = $tb->failure_output;
636             my $todo_output = $tb->todo_output;
637             my $current_test = $tb->current_test;
638              
639             $tb->output( File::Spec->devnull );
640             $tb->failure_output( File::Spec->devnull );
641             $tb->todo_output( File::Spec->devnull );
642              
643             my $result = $code->();
644              
645             $tb->output($output);
646             $tb->failure_output($failure_output);
647             $tb->todo_output($todo_output);
648             $tb->current_test($current_test);
649              
650             return $result;
651             }
652              
653             # Stick the END block in a method so we can test it.
654             END { Jifty::Test->_ending }
655              
656             sub _ending {
657             # only run the teardown code if we were responsible for setup
658             return unless $imported;
659              
660             my $Test = Jifty::Test->builder;
661              
662             # Such a hack -- try to detect if this is a forked child process and don't
663             # do cleanup in that case.
664             # TODO: note that this check fails if you're forking off multiple
665             # children that all do similar things, say running RT and Jifty tests
666             # in the same process
667             # XXX TODO - This makes assumptions about Test::Builder internals
668             return if $Test->{Original_Pid} != $$;
669              
670             my $should_die = 0;
671             if ($Jifty::SERVER &&
672             (my $plugin = Jifty->find_plugin("Jifty::Plugin::TestServerWarnings")) &&
673             grep { $_ eq 'Jifty::View::Declare::Handler' } Jifty->handler->view_handlers) { # testserverwarnings plugin requires TD handler to work properly.
674             my @warnings = $plugin->decoded_warnings( 'http://localhost:'.$Jifty::SERVER->port );
675              
676             $Test->diag("Uncaught warning: $_") for @warnings;
677             if ($WARNINGS_ARE_FATAL && @warnings) {
678             $Test->diag('Warnings not accepted in strict mode.');
679             $should_die = 1;
680             }
681             }
682              
683             # Turn off the server
684             undef $Jifty::SERVER;
685              
686             # If all tests passed..
687             if (Jifty::Test->is_passing && Jifty::Test->is_done) {
688             # Clean up mailbox
689             Jifty::Test->teardown_mailbox;
690              
691             # Disconnect the PubSub bus, if need be; otherwise we may not
692             # be able to drop the testing database. Calling ->bus, if we
693             # never dealt with PubSub in the test, can actually _do_ the
694             # connect now, unless we explicitly tell it not to.
695             Jifty->bus->disconnect
696             if Jifty->config and Jifty->bus( connect => 0 );
697              
698             # Remove testing db
699             if (Jifty->handle && !$ENV{JIFTY_FAST_TEST}) {
700             Jifty->handle->disconnect();
701             my $schema = Jifty::Script::Schema->new;
702             $schema->{drop_database} = 1;
703              
704             # The schema dropper dies when it can't drop the database
705             # this shouldn't kill tests
706             local $@;
707             eval { $schema->run };
708             if (my $err = $@) {
709             warn $err;
710             }
711             }
712              
713             # Unlink test files
714             unlink @Test_Files_To_Cleanup;
715             }
716              
717             # Cleanup the tempdirs
718             File::Temp::cleanup();
719              
720             # Unlink test file
721             unlink $Test->{test_config} if $Test->{test_config};
722             exit -1 if $should_die;
723             }
724              
725             =head1 SEE ALSO
726              
727             L, L
728              
729             =cut
730              
731             1;