1use strict;
2use warnings;
3
4use Test2::IPC;
5use Test2::Tools::Tiny;
6use Test2::API qw/context test2_ipc_drivers/;
7use Test2::Util qw/CAN_FORK CAN_THREAD CAN_REALLY_FORK/;
8
9{
10    package My::Formatter;
11
12    sub new { bless [], shift };
13
14    my $check = 1;
15    sub write {
16        my $self = shift;
17        my ($e, $count) = @_;
18        push @$self => $e;
19    }
20}
21
22{
23    package My::Event;
24
25    use base 'Test2::Event';
26    use Test2::Util::HashBase qw{msg};
27}
28
29tests basic => sub {
30    my $hub = Test2::Hub->new(
31        formatter => My::Formatter->new,
32    );
33
34    my $send_event = sub {
35        my ($msg) = @_;
36        my $e = My::Event->new(msg => $msg, trace => Test2::EventFacet::Trace->new(frame => ['fake', 'fake.t', 1]));
37        $hub->send($e);
38    };
39
40    ok(my $e1 = $send_event->('foo'), "Created event");
41    ok(my $e2 = $send_event->('bar'), "Created event");
42    ok(my $e3 = $send_event->('baz'), "Created event");
43
44    my $old = $hub->format(My::Formatter->new);
45
46    ok($old->isa('My::Formatter'), "old formatter");
47    is_deeply(
48        $old,
49        [$e1, $e2, $e3],
50        "Formatter got all events"
51    );
52};
53
54tests follow_ups => sub {
55    my $hub = Test2::Hub->new;
56    $hub->set_count(1);
57
58    my $trace = Test2::EventFacet::Trace->new(
59        frame => [__PACKAGE__, __FILE__, __LINE__],
60    );
61
62    my $ran = 0;
63    $hub->follow_up(sub {
64        my ($d, $h) = @_;
65        is_deeply($d, $trace, "Got trace");
66        is_deeply($h, $hub, "Got hub");
67        ok(!$hub->ended, "Hub state has not ended yet");
68        $ran++;
69    });
70
71    like(
72        exception { $hub->follow_up('xxx') },
73        qr/follow_up only takes coderefs for arguments, got 'xxx'/,
74        "follow_up takes a coderef"
75    );
76
77    $hub->finalize($trace);
78
79    is($ran, 1, "ran once");
80
81    is_deeply(
82        $hub->ended,
83        $trace->frame,
84        "Ended at the expected place."
85    );
86
87    eval { $hub->finalize($trace) };
88
89    is($ran, 1, "ran once");
90
91    $hub = undef;
92};
93
94tests IPC => sub {
95    my ($driver) = test2_ipc_drivers();
96    is($driver, 'Test2::IPC::Driver::Files', "Default Driver");
97    my $ipc = $driver->new;
98    my $hub = Test2::Hub->new(
99        formatter => My::Formatter->new,
100        ipc => $ipc,
101    );
102
103    my $build_event = sub {
104        my ($msg) = @_;
105        return My::Event->new(msg => $msg, trace => Test2::EventFacet::Trace->new(frame => ['fake', 'fake.t', 1]));
106    };
107
108    my $e1 = $build_event->('foo');
109    my $e2 = $build_event->('bar');
110    my $e3 = $build_event->('baz');
111
112    my $do_send = sub {
113        $hub->send($e1);
114        $hub->send($e2);
115        $hub->send($e3);
116    };
117
118    my $do_check = sub {
119        my $name = shift;
120
121        my $old = $hub->format(My::Formatter->new);
122
123        ok($old->isa('My::Formatter'), "old formatter");
124        is(@$old, 3, "Formatter got all events ($name)");
125        ok($_->{hubs}, "Set the hubs") for @$old;
126    };
127
128    if (CAN_REALLY_FORK) {
129        my $pid = fork();
130        die "Could not fork!" unless defined $pid;
131
132        if ($pid) {
133            is(waitpid($pid, 0), $pid, "waited properly");
134            ok(!$?, "child exited with success");
135            $hub->cull();
136            $do_check->('Fork');
137        }
138        else {
139            $do_send->();
140            exit 0;
141        }
142    }
143
144    if (CAN_THREAD && $] ge '5.010') {
145        require threads;
146        my $thr = threads->new(sub { $do_send->() });
147        $thr->join;
148        $hub->cull();
149        $do_check->('Threads');
150    }
151
152    $do_send->();
153    $hub->cull();
154    $do_check->('no IPC');
155};
156
157tests listen => sub {
158    my $hub = Test2::Hub->new();
159
160    my @events;
161    my @counts;
162    my $it = $hub->listen(sub {
163        my ($h, $e, $count) = @_;
164        is_deeply($h, $hub, "got hub");
165        push @events => $e;
166        push @counts => $count;
167    });
168
169    my $second;
170    my $it2 = $hub->listen(sub { $second++ });
171
172    my $ok1 = Test2::Event::Ok->new(
173        pass => 1,
174        name => 'foo',
175        trace => Test2::EventFacet::Trace->new(
176            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
177        ),
178    );
179
180    my $ok2 = Test2::Event::Ok->new(
181        pass => 0,
182        name => 'bar',
183        trace => Test2::EventFacet::Trace->new(
184            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
185        ),
186    );
187
188    my $ok3 = Test2::Event::Ok->new(
189        pass => 1,
190        name => 'baz',
191        trace => Test2::EventFacet::Trace->new(
192            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
193        ),
194    );
195
196    $hub->send($ok1);
197    $hub->send($ok2);
198
199    $hub->unlisten($it);
200
201    $hub->send($ok3);
202
203    is_deeply(\@counts, [1, 2], "Got counts");
204    is_deeply(\@events, [$ok1, $ok2], "got events");
205    is($second, 3, "got all events in listener that was not removed");
206
207    like(
208        exception { $hub->listen('xxx') },
209        qr/listen only takes coderefs for arguments, got 'xxx'/,
210        "listen takes a coderef"
211    );
212};
213
214tests metadata => sub {
215    my $hub = Test2::Hub->new();
216
217    my $default = { foo => 1 };
218    my $meta = $hub->meta('Foo', $default);
219    is_deeply($meta, $default, "Set Meta");
220
221    $meta = $hub->meta('Foo', {});
222    is_deeply($meta, $default, "Same Meta");
223
224    $hub->delete_meta('Foo');
225    is($hub->meta('Foo'), undef, "No Meta");
226
227    $hub->meta('Foo', {})->{xxx} = 1;
228    is($hub->meta('Foo')->{xxx}, 1, "Vivified meta and set it");
229
230    like(
231        exception { $hub->meta(undef) },
232        qr/Invalid META key: undef, keys must be true, and may not be references/,
233        "Cannot use undef as a meta key"
234    );
235
236    like(
237        exception { $hub->meta(0) },
238        qr/Invalid META key: '0', keys must be true, and may not be references/,
239        "Cannot use 0 as a meta key"
240    );
241
242    like(
243        exception { $hub->delete_meta(undef) },
244        qr/Invalid META key: undef, keys must be true, and may not be references/,
245        "Cannot use undef as a meta key"
246    );
247
248    like(
249        exception { $hub->delete_meta(0) },
250        qr/Invalid META key: '0', keys must be true, and may not be references/,
251        "Cannot use 0 as a meta key"
252    );
253};
254
255tests filter => sub {
256    my $hub = Test2::Hub->new();
257
258    my @events;
259    my $it = $hub->filter(sub {
260        my ($h, $e) = @_;
261        is($h, $hub, "got hub");
262        push @events => $e;
263        return $e;
264    });
265
266    my $count;
267    my $it2 = $hub->filter(sub { $count++; $_[1] });
268
269    my $ok1 = Test2::Event::Ok->new(
270        pass => 1,
271        name => 'foo',
272        trace => Test2::EventFacet::Trace->new(
273            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
274        ),
275    );
276
277    my $ok2 = Test2::Event::Ok->new(
278        pass => 0,
279        name => 'bar',
280        trace => Test2::EventFacet::Trace->new(
281            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
282        ),
283    );
284
285    my $ok3 = Test2::Event::Ok->new(
286        pass => 1,
287        name => 'baz',
288        trace => Test2::EventFacet::Trace->new(
289            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
290        ),
291    );
292
293    $hub->send($ok1);
294    $hub->send($ok2);
295
296    $hub->unfilter($it);
297
298    $hub->send($ok3);
299
300    is_deeply(\@events, [$ok1, $ok2], "got events");
301    is($count, 3, "got all events, even after other filter was removed");
302
303    $hub = Test2::Hub->new();
304    @events = ();
305
306    $hub->filter(sub { undef });
307    $hub->listen(sub {
308        my ($hub, $e) = @_;
309        push @events => $e;
310    });
311
312    $hub->send($ok1);
313    $hub->send($ok2);
314    $hub->send($ok3);
315
316    ok(!@events, "Blocked events");
317
318    like(
319        exception { $hub->filter('xxx') },
320        qr/filter only takes coderefs for arguments, got 'xxx'/,
321        "filter takes a coderef"
322    );
323};
324
325tests pre_filter => sub {
326    my $hub = Test2::Hub->new();
327
328    my @events;
329    my $it = $hub->pre_filter(sub {
330        my ($h, $e) = @_;
331        is($h, $hub, "got hub");
332        push @events => $e;
333        return $e;
334    });
335
336    my $count;
337    my $it2 = $hub->pre_filter(sub { $count++; $_[1] });
338
339    my $ok1 = Test2::Event::Ok->new(
340        pass => 1,
341        name => 'foo',
342        trace => Test2::EventFacet::Trace->new(
343            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
344        ),
345    );
346
347    my $ok2 = Test2::Event::Ok->new(
348        pass => 0,
349        name => 'bar',
350        trace => Test2::EventFacet::Trace->new(
351            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
352        ),
353    );
354
355    my $ok3 = Test2::Event::Ok->new(
356        pass => 1,
357        name => 'baz',
358        trace => Test2::EventFacet::Trace->new(
359            frame => [ __PACKAGE__, __FILE__, __LINE__ ],
360        ),
361    );
362
363    $hub->send($ok1);
364    $hub->send($ok2);
365
366    $hub->pre_unfilter($it);
367
368    $hub->send($ok3);
369
370    is_deeply(\@events, [$ok1, $ok2], "got events");
371    is($count, 3, "got all events, even after other pre_filter was removed");
372
373    $hub = Test2::Hub->new();
374    @events = ();
375
376    $hub->pre_filter(sub { undef });
377    $hub->listen(sub {
378        my ($hub, $e) = @_;
379        push @events => $e;
380    });
381
382    $hub->send($ok1);
383    $hub->send($ok2);
384    $hub->send($ok3);
385
386    ok(!@events, "Blocked events");
387
388    like(
389        exception { $hub->pre_filter('xxx') },
390        qr/pre_filter only takes coderefs for arguments, got 'xxx'/,
391        "pre_filter takes a coderef"
392    );
393};
394
395tests state => sub {
396    my $hub = Test2::Hub->new;
397
398    is($hub->count,      0,     "count starts at 0");
399    is($hub->failed,     0,     "failed starts at 0");
400    is($hub->is_passing, 1,     "start off passing");
401    is($hub->plan,       undef, "no plan yet");
402
403    $hub->is_passing(0);
404    is($hub->is_passing, 0, "Can Fail");
405
406    $hub->is_passing(1);
407    is($hub->is_passing, 1, "Passes again");
408
409    $hub->set_count(1);
410    is($hub->count,      1, "Added a passing result");
411    is($hub->failed,     0, "still no fails");
412    is($hub->is_passing, 1, "Still passing");
413
414    $hub->set_count(2);
415    $hub->set_failed(1);
416    is($hub->count,      2, "Added a result");
417    is($hub->failed,     1, "new failure");
418    is($hub->is_passing, 0, "Not passing");
419
420    $hub->is_passing(1);
421    is($hub->is_passing, 0, "is_passing always false after a failure");
422
423    $hub->set_failed(0);
424    $hub->is_passing(1);
425    is($hub->is_passing, 1, "Passes again");
426
427    $hub->set_failed(1);
428    is($hub->count,      2, "No new result");
429    is($hub->failed,     1, "new failure");
430    is($hub->is_passing, 0, "Not passing");
431
432    ok(!eval { $hub->plan('foo'); 1 }, "Could not set plan to 'foo'");
433    like($@, qr/'foo' is not a valid plan! Plan must be an integer greater than 0, 'NO PLAN', or 'SKIP'/, "Got expected error");
434
435    ok($hub->plan(5), "Can set plan to integer");
436    is($hub->plan, 5, "Set the plan to an integer");
437
438    $hub->set__plan(undef);
439    ok($hub->plan('NO PLAN'), "Can set plan to 'NO PLAN'");
440    is($hub->plan, 'NO PLAN', "Set the plan to 'NO PLAN'");
441
442    $hub->set__plan(undef);
443    ok($hub->plan('SKIP'), "Can set plan to 'SKIP'");
444    is($hub->plan, 'SKIP', "Set the plan to 'SKIP'");
445
446    ok(!eval { $hub->plan(5); 1 }, "Cannot change plan");
447    like($@, qr/You cannot change the plan/, "Got error");
448
449    my $trace = Test2::EventFacet::Trace->new(frame => ['Foo::Bar', 'foo.t', 42, 'blah']);
450    $hub->finalize($trace);
451    my $ok = eval { $hub->finalize($trace) };
452    my $err = $@;
453    ok(!$ok, "died");
454
455    is($err, <<"    EOT", "Got expected error");
456Test already ended!
457First End:  foo.t line 42
458Second End: foo.t line 42
459    EOT
460
461    $hub = Test2::Hub->new;
462
463    $hub->plan(5);
464    $hub->set_count(5);
465    $hub->set_failed(1);
466    $hub->set_ended($trace);
467    $hub->set_bailed_out("foo");
468    $hub->set_skip_reason('xxx');
469    ok(!$hub->is_passing, "not passing");
470
471    $hub->reset_state;
472
473    ok(!$hub->plan, "no plan");
474    is($hub->count, 0, "count reset to 0");
475    is($hub->failed, 0, "reset failures");
476    ok(!$hub->ended, "not ended");
477    ok(!$hub->bailed_out, "did not bail out");
478    ok(!$hub->skip_reason, "no skip reason");
479};
480
481done_testing;
482