epic-s6ts
[kivitendo-erp.git] / SL / BackgroundJob / SelfTest.pm
1 package SL::BackgroundJob::SelfTest;
2
3 use strict;
4
5 use parent qw(SL::BackgroundJob::Base);
6
7 use Test::Builder;
8 use TAP::Parser;
9 use TAP::Parser::Aggregator;
10 use Sys::Hostname;
11 use FindBin;
12
13 use SL::DB::AuthUser;
14 use SL::DB::Default;
15 use SL::Common;
16 use SL::Locale::String qw(t8);
17 use Carp;
18
19 use Rose::Object::MakeMethods::Generic (
20   array => [
21    'modules'     => {},
22    'add_modules' => { interface => 'add', hash_key => 'modules' },
23    'errors'      => {},
24    'add_errors'  => { interface => 'add', hash_key => 'errors' },
25    'full_diag'      => {},
26    'add_full_diag'  => { interface => 'add', hash_key => 'full_diag' },
27   ],
28   scalar => [
29    qw(diag tester config aggreg module_nr),
30   ],
31 );
32
33 sub create_job {
34   $_[0]->create_standard_job('20 2 * * *'); # every day at 2:20 am
35 }
36
37 sub setup {
38   my ($self) = @_;
39
40   $self->config($::lx_office_conf{self_test} || {});
41
42   $self->tester(Test::Builder->new);
43   $self->tester->reset; # stupid Test::Builder mplementation uses class variables
44   $self->aggreg(TAP::Parser::Aggregator->new);
45
46   $self->modules(split /\s+/, $self->config->{modules});
47 }
48
49 sub run {
50   my $self        = shift;
51   $self->setup;
52
53   return 1 unless $self->modules;
54
55   foreach my $module ($self->modules) {
56     $self->run_module($module);
57   }
58
59   $self->log(
60     sprintf "SelfTest status: %s, passed: %s, failed: %s, unexpectedly succeeded: %s",
61              $self->aggreg->get_status,
62              $self->aggreg->passed,
63              $self->aggreg->failed,
64              $self->aggreg->todo_passed,
65   );
66   # if (!$self->aggreg->all_passed || $self->config->{send_email_on_success}) {
67   # all_passed is not set or calculated (anymore). it is safe to check only for probs or errors
68   if ($self->aggreg->failed || $self->config->{send_email_on_success}) {
69     $self->_send_email;
70   }
71
72   croak t8("Unsuccessfully executed:" . join ("\n", $self->errors)) if $self->errors;
73   return 1;
74 }
75
76 sub run_module {
77   my ($self, $module) = @_;
78
79   # TAP usually prints out to STDOUT and STDERR, capture those for TAP::Parser
80   my $output;
81
82   $self->tester->output        (\$output);
83   $self->tester->failure_output(\$output);
84   $self->tester->todo_output   (\$output);
85
86   # sanitize module name;
87   # this allows unicode package names, which are known to be buggy in 5.10, you should avoid them
88   $module =~ s/[^\w:]//g;
89   $module = "SL::BackgroundJob::SelfTest::$module";
90
91   # increase module nr
92   $self->module_nr(($self->module_nr || 0) + 1);
93
94   # try to load module;
95   (my $file = $module) =~ s|::|/|g;
96   eval {
97     require $file . '.pm';
98     1
99   } or $self->add_errors($::locale->text('Could not load class #1 (#2): "#3"', $module, $file, $@)) && return;
100
101   eval {
102     $self->tester->subtest($module => sub {
103       $module->new->run;
104     });
105   1
106   } or $self->add_errors($::locale->text('Could not load class #1, #2', $module, $@)) && return;
107
108   $self->add_full_diag($output);
109   $self->{diag_per_module}{$self->module_nr . ': ' . $module} = $output;
110
111   my $parser = TAP::Parser->new({ tap => $output});
112   $parser->run;
113
114   $self->aggreg->add($module => $parser);
115 }
116
117 sub _email_user {
118   $_[0]{email_user} ||= SL::DB::Manager::AuthUser->find_by(login => $_[0]->config->{send_email_to});
119 }
120
121 sub _send_email {
122   my ($self) = @_;
123
124   return if !$self->config || !$self->config->{send_email_to};
125
126   my $user  = $self->_email_user;
127   my $email = $user ? $user->get_config_value('email') : undef;
128
129   return unless $email;
130
131   my ($output, $content_type) = $self->_prepare_report;
132
133   my $mail              = Mailer->new;
134   $mail->{from}         = $self->config->{email_from};
135   $mail->{to}           = $email;
136   $mail->{subject}      = $self->config->{email_subject};
137   $mail->{content_type} = $content_type;
138   $mail->{message}      = $$output;
139
140   my $err = $mail->send;
141   $self->add_errors('Mailer error #1', $err) if $err;
142
143 }
144
145 sub _prepare_report {
146   my ($self) = @_;
147
148   my $template = Template->new({ 'INTERPOLATE' => 0,
149                                  'EVAL_PERL'   => 0,
150                                  'ABSOLUTE'    => 1,
151                                  'CACHE_SIZE'  => 0,
152                                });
153
154   return unless $template;
155   my $email_template = $self->config->{email_template};
156   my $filename       = $email_template || ( (SL::DB::Default->get->templates || "templates/mails") . "/self_test/status_mail.txt" );
157   my $content_type   = $filename =~ m/.html$/ ? 'text/html' : 'text/plain';
158
159
160   my %params = (
161     SELF     => $self,
162     host     => hostname,
163     database => $::auth->client->{dbname},
164     client   => $::auth->client->{name},
165     path     => $FindBin::Bin,
166     errors   => $self->errors,
167   );
168
169   my $output;
170   $template->process($filename, \%params, \$output) || die $template->error;
171
172   return (\$output, $content_type);
173 }
174
175 sub log {
176   my $self = shift;
177   $::lxdebug->message(0, "[" . __PACKAGE__ . "] @_") if $self->config->{log_to_file};
178 }
179
180
181 1;
182
183 __END__
184
185 =head1 NAME
186
187 SL::BackgroundJob::SelfTest - pluggable self testing
188
189 =head1 SYNOPSIS
190
191   use SL::BackgroundJob::SelfTest;
192   SL::BackgroundJob::SelfTest->new->run;;
193
194 =cut