File Coverage

blib/lib/Test/Log4perl.pm
Criterion Covered Total %
statement 149 174 85.6
branch 30 36 83.3
condition 13 18 72.2
subroutine 32 39 82.0
pod 4 12 33.3
total 228 279 81.7


line stmt bran cond sub pod time code
1             package Test::Log4perl;
2 4     4   400006 use base qw(Class::Accessor::Chained);
  4         10  
  4         4287  
3             __PACKAGE__->mk_accessors(qw(category));
4              
5 4     4   15730 use strict;
  4         9  
  4         116  
6 4     4   23 use warnings;
  4         14  
  4         138  
7              
8 4     4   21 use Test::Builder;
  4         8  
  4         149  
9             my $Tester = Test::Builder->new();
10              
11 4     4   4463 use Lingua::EN::Numbers::Ordinate;
  4         2083  
  4         302  
12 4     4   27 use Carp qw(croak);
  4         6  
  4         209  
13 4     4   24 use Scalar::Util qw(blessed);
  4         10  
  4         426  
14 4     4   1489 use Log::Log4perl qw(:levels);
  4         68454  
  4         38  
15              
16             our $VERSION = '0.1001';
17              
18             =head1 NAME
19              
20             Test::Log4perl - test log4perl
21              
22             =head1 SYNOPSIS
23              
24             use Test::More tests => 1;
25              
26             # setup l4p
27             use Log::Log4Perl;
28             # do your normal Log::Log4Perl setup here
29             use Test::Log4perl;
30              
31             # get the loggers
32             my $logger = Log::Log4perl->get_logger("Foo::Bar");
33             my $tlogger = Test::Log4perl->get_logger("Foo::Bar");
34              
35             # test l4p
36             Test::Log4perl->start();
37              
38             # declare we're going to log something
39             $tlogger->error("This is a test");
40              
41             # log that something
42             $logger->error("This is a test");
43              
44             # test that those things matched
45             Test::Log4perl->end("Test that that logs okay");
46            
47             # we also have a simplified version:
48             {
49             my $foo = Test::Logger->expect(['foo.bar.quux', warn => qr/hello/ ]);
50             # ... do something that should log 'hello'
51             }
52             # $foo goes out of scope; this triggers the test.
53              
54             =head1 DESCRIPTION
55              
56             This module can be used to test that you're logging the right thing
57             with Log::Log4perl. It checks that we get what, and only what, we
58             expect logged by your code.
59              
60             The basic process is very simple. Within your test script you get
61             one or more loggers from B with the C method
62             just like you would with B. You're going to use these
63             loggers to declare what you think the code you're going to test should
64             be logging.
65              
66             # declare a bunch of test loggers
67             my $tlogger = Test::Log4perl->get_logger("Foo::Bar");
68              
69             Then, for each test you want to do you need to start up the module.
70              
71             # start the test
72             Test::Log4perl->start();
73              
74             This diverts all subsequent attempts B makes to log
75             stuff and records them internally rather than passing them though to
76             the Log4perl appenders as normal.
77              
78             You then need to declare with the loggers we created earlier what
79             we hope Log4perl will be asked to log. This is the same syntax as
80             Test::Log4perl uses, except if you want you can use regular expressions:
81              
82             $tlogger->debug("fish");
83             $tlogger->warn(qr/bar/);
84              
85             You then need to run your code that you're testing.
86              
87             # call some code that hopefully will call the log4perl methods
88             # 'debug' with "fish" and 'warn' with something that contains 'bar'
89             some_code();
90              
91             We finally need to tell B that we're done and it
92             should do the comparisons.
93              
94             # start the test
95             Test::Log4perl->end("test name");
96              
97             =head2 Methods
98              
99             =over
100              
101             =item get_logger($category)
102              
103             Returns a new instance of Test::Logger that can be used to log
104             expected messages in the category passed.
105              
106             =cut
107              
108             sub get_logger
109             {
110 5     5 1 1102 my $class = shift;
111 5         15 my $self = bless {}, $class;
112 5         23 $self->category(shift);
113 5         130 return $self;
114             }
115              
116             =item Test::Logger->expect(['dotted.path', 'warn' => qr'this', 'warn' => qr'that'], ..)
117              
118             Class convenience method. Used like this:
119              
120             { # start local scope
121             my $foo = Test::Logger->expect(['foo.bar.quux', warn => qr/hello/ ]);
122             # ... do something that should log 'hello'
123             } # $foo goes out of scope; this triggers the test.
124              
125             =cut
126              
127             sub expect {
128 0     0 1 0 my ($class, @expects) = @_;
129 0         0 my @loggers;
130 0         0 $class->start(ignore_priority => "info");
131 0         0 for (@expects) {
132 0         0 my $name = shift @$_;
133 0         0 my $tlogger = $class->get_logger($name);
134             # XXX: respect current loglevel
135 0         0 while (my ($level, $what) = splice(@$_, 0, 2)) {
136 0         0 $tlogger->$level($what);
137             }
138 0         0 push @loggers, $tlogger;
139             }
140 0         0 return \@loggers;
141             }
142              
143              
144             =item start
145              
146             Class method. Start logging. When you call this method it temporarly
147             redirects all logging from the standard logging locations to the
148             internal logging routine until end is called. Takes parameters to
149             change the behavior of this (and only this) test. See below.
150              
151             =cut
152              
153             # convet a string priority into a digit one
154             sub _to_d($)
155             {
156 8     8   10 my $priority = shift;
157              
158             # check the priority is all digits
159 8 50       29 if ($priority =~ /\D/)
160             {
161 8 100       22 if (lc($priority) eq "everything") { $priority = $OFF }
  3 100       4  
162 2         4 elsif (lc($priority) eq "nothing") { $priority = $ALL }
163 3         14 else { $priority = Log::Log4perl::Level::to_priority(uc $priority) }
164             }
165              
166 8         34 return $priority;
167             }
168              
169             # the list of things we've stored so far
170             our @expected;
171             our @logged;
172              
173             sub start
174             {
175 24     24 1 8922 my $class = shift;
176 24         50 my %args = @_;
177              
178             # empty the record
179 24         46 @logged = ();
180 24         36 @expected = ();
181 24         67 $class->interception_class->reset_temp;
182              
183             # the priority
184 24 50       74 if ($args{ignore_everything})
185 0         0 { $args{ignore_priority} = "everything" }
186 24 50       110 if ($args{ignore_nothing})
187 0         0 { $args{ignore_priority} = "nothing" }
188 24 100       66 if (exists $args{ignore_priority})
189 3         9 { $class->interception_class->set_temp("ignore_priority",_to_d $args{ignore_priority}) }
190              
191             # turn on the interception code
192 24         61 foreach (values %$Log::Log4perl::Logger::LOGGERS_BY_NAME)
193 48         108 { bless $_, $class->interception_class }
194             }
195              
196             =item debug(@what)
197              
198             =item info(@what)
199              
200             =item warn(@what)
201              
202             =item error(@what)
203              
204             =item fatal(@what)
205              
206             Instance methods. String of things that you're expecting to log, at
207             the level you're expecting them, in what class.
208              
209             =cut
210              
211             sub _log_at_level
212             {
213 26     26   29 my $self = shift;
214 26         30 my $priority = shift;
215 26         34 my $message = shift;
216              
217 26         72 push @expected, {
218             category => $self->category,
219             priority => $priority,
220             message => $message,
221             };
222             }
223              
224             foreach my $level (qw(debug info warn error fatal))
225             {
226 4     4   2856 no strict 'refs';
  4         29  
  4         4690  
227             *{$level} = sub {
228 26     26   218 my $class = shift;
229 26         56 $class->_log_at_level($level, @_)
230             }
231             }
232              
233             =item end()
234              
235             =item end($name)
236              
237             Ends the test and compares what we've got with what we expected.
238             Switches logging back from being captured to going to wherever
239             it was originally directed in the config.
240              
241             =cut
242              
243             # eeek, the hard bit
244             sub end
245             {
246 22     22 1 947 my $class = shift;
247 22   50     83 my $name = shift || "Log4perl test";
248              
249 22         52 $class->interception_class->set_temp("ended", 1);
250             # turn off the interception code
251 22         56 foreach (values %$Log::Log4perl::Logger::LOGGERS_BY_NAME)
252 44         92 { bless $_, $class->original_class }
253              
254 22         30 my $no;
255 22         62 while (@logged)
256             {
257 25         29 $no++;
258              
259 25         33 my $logged = shift @logged;
260 25         33 my $expected = shift @expected;
261              
262             # not expecting anything?
263 25 100       51 unless ($expected)
264             {
265 1         12 $Tester->ok(0,$name);
266 1         584 $Tester->diag("Unexpected $logged->{priority} of type '$logged->{category}':\n");
267 1         74 $Tester->diag(" '$logged->{message}'");
268 1         69 return 0;
269             }
270              
271             # was this message what we expected?
272             # ...
273 15         41 my %wrong = map { $_ => 1 }
  72         146  
274 24         40 grep { !_matches($logged->{ $_ }, $expected->{ $_ }) }
275             qw(category message priority);
276 24 100       100 if (%wrong)
277             {
278 9         32 $Tester->ok(0, $name);
279 9         4903 $Tester->diag(ordinate($no)." message logged wasn't what we expected:");
280 9         778 foreach my $thingy (qw(category priority message))
281             {
282 27 100       576 if ($wrong{ $thingy })
283             {
284 15         85 $Tester->diag(sprintf(q{ %8s was '%s'}, $thingy, $logged->{ $thingy }));
285 15 100 66     1017 if (ref($expected->{ $thingy }) && ref($expected->{ $thingy }) eq "Regexp")
286 1         6 { $Tester->diag(" not like '$expected->{$thingy}'") }
287             else
288 14         51 { $Tester->diag(" not '$expected->{$thingy}'") }
289             }
290             }
291 9         484 $Tester->diag(" (Offending log call from line $logged->{line} in $logged->{filename})");
292              
293 9         748 return 0
294              
295             }
296             }
297              
298             # expected something but didn't get it?
299 12 100       29 if (@expected)
300             {
301 2         8 $Tester->ok(0, $name);
302 2         1057 $Tester->diag("Ended logging run, but still expecting ".@expected." more log(s)");
303 2         127 $Tester->diag("Expecting $expected[0]{priority} of type '$expected[0]{category}' next:");
304 2         136 $Tester->diag(" '$expected[0]{message}'");
305 2         127 return 0;
306             }
307              
308 10         40 $Tester->ok(1,$name);
309 10         3040 return 1;
310             }
311              
312             # this is essentially a trivial implementation of perl 6's smart match operator
313             sub _matches
314             {
315 80     80   588 my $got = shift;
316 80         82 my $expected = shift;
317              
318 80         105 my $ref = ref($expected);
319              
320             # compare as a string
321 80 100       153 unless ($ref)
322 72         224 { return $expected eq $got }
323              
324             # compare a regex?
325 8 100       32 if (ref($expected) eq "Regexp")
326 4         31 { return $got =~ $expected }
327              
328             # check if it's a reference to something, and die
329 4 100       28 if (!blessed($expected))
330 1         26 { croak "Don't know how to compare a reference to a $ref" }
331              
332             # it's an object. Is that overloaded in some way?
333             # (note we avoid calling overload::Overloaded unless someone has used
334             # the module first)
335 3 100 66     18 if (defined(&overload::Overloaded) && overload::Overloaded($expected))
336 2         101 { return $expected eq $got }
337            
338 1         1996 croak "Don't know how to compare object $ref";
339             }
340              
341             =back
342              
343             =head2 Ignoring All Logging Messages
344              
345             Sometimes you're going to be testing something that generates a load
346             of spurious log messages that you simply want to ignore without
347             testing their contents, but you don't want to have to reconfigure
348             your log file. The simpliest way to do this is to do:
349              
350             use Test::Log4perl;
351             Test::Log4perl->suppress_logging;
352              
353             All logging functions stop working. Do not alter the Logging classes
354             (for example, by changing the config file and use Log4perl's
355             C functionality) after this call has been made.
356              
357             This function will be effectivly a no-op if the enviromental variable
358             C is set to a true value (so if your code is
359             behaving weirdly you can turn all the logging back on from the command
360             line without changing any of the code)
361              
362             =cut
363              
364             # TODO: What if someone calls ->start() after this then, eh?
365             # currently it'll test the logs and then stop supressing logging
366             # is that what we want? Because that's what'll happen.
367              
368             # I canna spell
369 0     0 0 0 sub supress_logging { my $class = shift; $class->supress_logging(@_) }
  0         0  
370              
371             sub suppress_logging
372             {
373 0     0 0 0 my $class = shift;
374              
375 0 0       0 return if $ENV{NO_SUPRESS_LOGGING};
376              
377             # tell this to ignore everything.
378 0         0 foreach (values %$Log::Log4perl::Logger::LOGGERS_BY_NAME)
379 0         0 { bless $_, $class->ignore_all_class }
380             }
381              
382             =head2 Selectivly Ignoring Logging Messages By Priority
383              
384             It's a bad idea to completely ignore all messages. What you probably
385             want to do is ignore some of the trivial messages that you don't
386             care about, and just test that there aren't any unexpected messages
387             of a set priority.
388              
389             You can temporarly ignore any logging messages that are made by
390             passing parameters to the C routine
391              
392             # for this test, just ignore DEBUG, INFO, and WARN
393             Test::Log4perl->start( ignore_priority => "warn" );
394              
395             # you can use the levels constants to do the same thing
396             use Log::Log4perl qw(:levels);
397             Test::Log4perl->start( ignore_priority => $WARN );
398              
399             You might want to ignore all logging events at all (this can be used
400             as quick way to not test the actual log messages, but just ignore the
401             output.
402              
403             # for this test, ignore everything
404             Test::Log4perl->start( ignore_priority => "everything" );
405              
406             # contary to readability, the same thing (try not to write this)
407             use Log::Log4perl qw(:levels);
408             Test::Log4perl->start( ignore_priority => $OFF );
409              
410             Or you might want to not ignore anything (which is the default, unless
411             you've played with the method calls mentioned below:)
412              
413             # for this test, ignore nothing
414             Test::Log4perl->start( ignore_priority => "nothing" );
415              
416             # contary to readability, the same thing (try not to write this)
417             use Log::Log4perl qw(:levels);
418             Test::Log4perl->start( ignore_priority => $ALL );
419              
420             You can also perminatly effect what things are ignored with the
421             C method call. This persists between tests and isn't
422             autoically reset after each call to C.
423              
424             # ignore DEBUG, INFO and WARN for all future tests
425             Test::Log4perl->ignore_priority("warn");
426              
427             # you can use the levels constants to do the same thing
428             use Log::Log4perl qw(:levels);
429             Test::Log4perl->ignore_priority($WARN);
430              
431             # ignore everything (no log messages will be logged)
432             Test::Log4perl->ignore_priority("everything");
433              
434             # ignore nothing (messages will be logged reguardless of priority)
435             Test::Log4perl->ignore_priority("nothing");
436              
437             Obviously, you may temporarly override whatever perminant
438              
439             =cut
440              
441             sub ignore_priority
442             {
443 5     5 0 21 my $class = shift;
444 5         8 my $p = _to_d shift;
445 5         11 $class->interception_class->set_temp("ignore_priority", $p);
446 5         11 $class->interception_class->set_perm("ignore_priority", $p);
447             }
448              
449             sub ignore_everything
450             {
451 0     0 0 0 my $class = shift;
452 0         0 $class->ignore_priority($OFF);
453             }
454              
455             sub ignore_nothing
456             {
457 0     0 0 0 my $class = shift;
458 0         0 $class->ignore_priority($ALL);
459             }
460              
461 112     112 0 419 sub interception_class { "Log::Log4perl::Logger::Interception" }
462 0     0 0 0 sub ignore_all_class { "Log::Log4perl::Logger::IgnoreAll" }
463 44     44 0 126 sub original_class { "Log::Log4perl::Logger" }
464              
465             sub DESTROY {
466 5 50   5   3288 return if $_[0]->interception_class->ended;
467 0         0 goto $_[0]->can('end');
468             }
469              
470             ###################################################################################################
471              
472             package Log::Log4perl::Logger::Interception;
473 4     4   62 use base qw(Log::Log4perl::Logger);
  4         7  
  4         432  
474 4     4   21 use Log::Log4perl qw(:levels);
  4         8  
  4         19  
475              
476             our %temp;
477             our %perm;
478              
479 24     24   51 sub reset_temp { %temp = () }
480 30     30   48 sub set_temp { my ($class, $key, $val) = @_; $temp{$key} = $val }
  30         66  
481 5     5   7 sub set_perm { my ($class, $key, $val) = @_; $perm{$key} = $val }
  5         13  
482 5     5   11 sub ended { my ($class) = @_; $temp{ended} }
  5         1675  
483             # all the basic logging functions
484             foreach my $level (qw(debug info warn error fatal))
485             {
486 4     4   1176 no strict 'refs';
  4         8  
  4         1466  
487              
488             # we need to pass the number to log
489             my $level_int = Log::Log4perl::Level::to_priority(uc($level));
490             *{$level} = sub {
491 61     61   437 my $self = shift;
492 61         137 $self->log($level_int, @_)
493             }
494             }
495              
496             sub log
497             {
498 61     61   87 my $self = shift;
499 61         67 my $priority = shift;
500 61         82 my $message = shift;
501              
502             # are we logging anything or what?
503 61 100 100     419 if ($priority <= ($temp{ignore_priority} || 0) or
      100        
      100        
504             $priority <= ($perm{ignore_priority} || 0))
505 24         55 { return }
506              
507             # what's that priority called then?
508 37         100 my $priority_name = lc( Log::Log4perl::Level::to_level($priority) );
509              
510             # find the filename and line
511 37         244 my ($filename, $line);
512 37         61 my $cur_filename = _cur_filename();
513 37         51 my $level = 1;
514 37   33     43 do {
515 37         438 (undef, $filename, $line) = caller($level++);
516             } while ($filename eq $cur_filename || $filename eq $INC{"Log/Log4perl/Logger.pm"});
517              
518             # log it
519 37         188 push @Test::Log4perl::logged, {
520             category => $self->{category}, # oops, there goes encapsulation
521             priority => $priority_name,
522             message => $message,
523             filename => $filename,
524             line => $line,
525             };
526              
527 37         108 return;
528             }
529              
530 37     37   98 sub _cur_filename { (caller)[1] }
531              
532             1;
533              
534             package Log::Log4perl::Logger::IgnoreAll;
535 4     4   24 use base qw(Log::Log4perl::Logger);
  4         8  
  4         383  
536              
537             # all the functions we don't want
538             foreach my $level (qw(debug info warn error fatal log))
539             {
540 4     4   29 no strict 'refs';
  4         8  
  4         416  
541 0     0     *{$level} = sub { return () }
542             }
543              
544             =head1 BUGS
545              
546             Logging methods don't return the number of appenders they've written
547             to (or rather, they do, as it's always zero.)
548              
549             Changing the config file (if you're watching it) while this is testing
550             / supressing everything will probably break everything. As will
551             creating new appenders, etc...
552              
553             =head1 AUTHOR
554              
555             Mark Fowler
556              
557             =head1 COPYRIGHT
558              
559             Copyright 2005 Fotango Ltd all rights reserved.
560             Licensed under the same terms as Perl itself.
561              
562             =cut
563              
564             1;