File Coverage

blib/lib/Tapper/Cmd/Testrun.pm
Criterion Covered Total %
statement 144 181 79.5
branch 42 78 53.8
condition 19 36 52.7
subroutine 19 22 86.3
pod 10 10 100.0
total 234 327 71.5


line stmt bran cond sub pod time code
1             package Tapper::Cmd::Testrun;
2             our $AUTHORITY = 'cpan:TAPPER';
3             $Tapper::Cmd::Testrun::VERSION = '5.0.11';
4 4     4   5739528 use Moose;
  4         511821  
  4         35  
5 4     4   29485 use Tapper::Model 'model';
  4         4021  
  4         252  
6 4     4   26 use DateTime;
  4         14  
  4         126  
7 4     4   1430 use Perl6::Junction qw/any/;
  4         20893  
  4         284  
8 4     4   1893 use Hash::Merge::Simple qw/merge/;
  4         2151  
  4         244  
9 4     4   30 use Try::Tiny;
  4         10  
  4         206  
10              
11 4     4   25 use parent 'Tapper::Cmd';
  4         8  
  4         30  
12 4     4   2176 use Tapper::Cmd::Requested;
  4         11  
  4         133  
13 4     4   1919 use Tapper::Cmd::Precondition;
  4         12  
  4         140  
14 4     4   1901 use Tapper::Cmd::Notification;
  4         12  
  4         8407  
15              
16              
17              
18             sub find_matching_hosts
19             {
20 10     10 1 42 return;
21             }
22              
23              
24              
25             sub create
26             {
27 10     10 1 6988 my ($self, $plan, $instance) = @_;
28              
29 10 50       86 if ( not ref($plan) ) {
30 0         0 require YAML::Syck;
31 0         0 $plan = YAML::Syck::Load($plan);
32             }
33              
34 10 50       39 if ( not ref($plan) eq 'HASH' ) {
35 0         0 die "'$plan' is not YAML containing a testrun description\n";
36             }
37              
38             my @preconditions = Tapper::Cmd::Precondition
39             ->new({ schema => $self->schema })
40             ->add($plan->{preconditions})
41 10         340 ;
42              
43 10 100       443142 my %args = map { lc($_) => $plan->{$_} } grep { lc($_) ne 'preconditions' and $_ !~ /^requested/i } keys %$plan;
  13         53  
  45         1601  
44              
45 10         33 my @testruns;
46 10 50       45 foreach my $host (@{$plan->{requested_hosts_all} || [] }) {
  10         56  
47             my $merged_arguments = merge \%args, {precondition => $plan->{preconditions},
48 14         197 requested_hosts => $host,
49             testplan_id => $instance,
50             };
51 14         882 my $testrun_id = $self->add($merged_arguments);
52 14         161 $self->assign_preconditions($testrun_id, @preconditions);
53 14         782170 push @testruns, $testrun_id;
54             }
55 10 100       113 if ($plan->{requested_hosts_any}) {
56             my $merged_arguments = merge \%args, {precondition => $plan->{preconditions},
57             requested_hosts => $plan->{requested_hosts_any},
58 4         66 testplan_id => $instance};
59 4         255 my $testrun_id = $self->add($merged_arguments );
60 4         42 $self->assign_preconditions($testrun_id, @preconditions);
61 4         389441 push @testruns, $testrun_id;
62             }
63 10         79 foreach my $host ($self->find_matching_hosts($plan->{requested_features_all})) {
64             my $merged_arguments = merge \%args, {precondition => $plan->{preconditions},
65 0         0 requested_hosts => $host,
66             testplan_id => $instance};
67 0         0 my $testrun_id = $self->add($merged_arguments );
68 0         0 $self->assign_preconditions($testrun_id, @preconditions);
69 0         0 push @testruns, $testrun_id;
70             }
71 10 100       55 if ($plan->{requested_features_any}) {
72             my $merged_arguments = merge \%args, {precondition => $plan->{preconditions},
73             requested_features => $plan->{requested_features_any},
74 4         55 testplan_id => $instance};
75 4         276 my $testrun_id = $self->add($merged_arguments );
76 4         45 $self->assign_preconditions($testrun_id, @preconditions);
77 4         378803 push @testruns, $testrun_id;
78             }
79 10 50       63 if ( not grep { $_ =~ /^requested/i } keys %$plan) {
  45         200  
80             my $merged_arguments = merge \%args, {precondition => $plan->{preconditions},
81 0         0 testplan_id => $instance,
82             };
83 0         0 my $testrun_id = $self->add($merged_arguments);
84 0         0 $self->assign_preconditions($testrun_id, @preconditions);
85 0         0 push @testruns, $testrun_id;
86             }
87              
88 10         106 return @testruns;
89              
90             }
91              
92              
93              
94              
95             sub add {
96              
97 23     23 1 27995 my ($self, $received_args) = @_;
98              
99 23 50       59 my %args = %{$received_args || {}}; # copy
  23         180  
100              
101 23   100     257 $args{notes} ||= '';
102 23   100     162 $args{shortname} ||= '';
103              
104 23         63 my ( $testrun_id, $exception );
105 23         1029 my $or_schema = $self->schema;
106              
107             try {
108             $or_schema->txn_do(sub {
109              
110 23   100     11585 $args{topic_name} = $args{topic} || 'Misc';
111 23         129 my $topic = $or_schema->resultset('Topic')->find_or_create({name => $args{topic_name}});
112            
113 23   66     90128 $args{earliest} ||= DateTime->now;
114 23   50     11449 $args{owner} ||= $ENV{USER} || 'nobody';
      66        
115 23   33     293 $args{owner_id} ||= Tapper::Model::get_or_create_owner( $args{owner} );
116            
117 23 100 66     111448 if ($args{requested_hosts} and not $args{requested_host_ids}) {
118 19 100       720 foreach my $host (@{ref $args{requested_hosts} eq 'ARRAY' ? $args{requested_hosts} : [ $args{requested_hosts} ]}) {
  19         115  
119 24         303 my $host_result = $or_schema->resultset('Host')->search({name => $host}, {rows => 1})->first;
120 24 50       96067 die "Can not request host '$host'. This host is not known to tapper\n" if not $host_result;
121 24 50       4164 push @{$args{requested_host_ids}}, $host_result->id if $host_result;
  24         778  
122             }
123             }
124            
125 23 50       1023 if ( not $args{queue_id} ) {
126 23   100     161 $args{queue} ||= 'AdHoc';
127 23         106 my $queue_result = $or_schema->resultset('Queue')->search({name => $args{queue}});
128 23 50       13576 die qq{Queue "$args{queue}" does not exists\n} if not $queue_result->count;
129 23         89133 $args{queue_id} = $queue_result->search({}, {rows => 1})->first->id;
130             }
131              
132 23         58667 $testrun_id = $or_schema->resultset('Testrun')->add(\%args);
133              
134 23 100       241784 if ( $args{requested_features} ) {
135 4         71 foreach my $feature (
136             @{
137             ref $args{requested_features} eq 'ARRAY'
138             ? $args{requested_features}
139 4 50       31 : [ $args{requested_features} ]
140             }
141             ) {
142 5         1852 $or_schema
143             ->resultset('TestrunRequestedFeature')
144             ->new({testrun_id => $testrun_id, feature => $feature})
145             ->insert()
146             ;
147             }
148             }
149              
150 23 100       7551 if ( exists $args{notify} ) {
151 1   50     6 my $s_notify = $args{notify} // q##;
152 1         24 my $notify = Tapper::Cmd::Notification->new();
153 1         561 my $filter = "testrun('id') == $testrun_id";
154 1 50       8 if (lc $args{notify} eq any('pass', 'ok','success')) {
    0          
155 1         91 $filter .= " and testrun('success_word') eq 'pass'";
156             } elsif (lc $args{notify} eq any('fail', 'not_ok','error')) {
157 0         0 $filter .= " and testrun('success_word') eq 'fail'";
158             }
159             try {
160             $notify->add({filter => $filter,
161             owner_id => $args{owner_id},
162 1         71 event => "testrun_finished",
163             });
164             } catch {
165 0         0 $exception = "Successfully created your testrun with id $testrun_id but failed to add a notification request\n$_";
166             }
167 1         16 }
168 23     23   1451 });
169             }
170             catch {
171 0     0   0 $exception = $_;
172 23         275 };
173              
174 23 50       296912 if ( wantarray ) {
175 0         0 return ( $testrun_id, $exception );
176             }
177             else {
178 23 50       122 if ( $exception ) {
179 0         0 die $exception;
180             }
181 23         288 return $testrun_id;
182             }
183              
184             }
185              
186              
187              
188             sub update {
189 1     1 1 42375 my ($self, $id, $args) = @_;
190 1         3 my %args = %{$args}; # copy
  1         6  
191              
192 1         40 my $testrun = $self->schema->resultset('Testrun')->find($id);
193              
194 1 50 0     4452 $args{owner_id} = $args{owner_id} || Tapper::Model::get_or_create_owner( $args{owner} ) if $args{owner};
195              
196 1         24 return $testrun->update_content(\%args);
197             }
198              
199              
200             sub del {
201 1     1 1 136477 my ($self, $id) = @_;
202 1         39 my $testrun = $self->schema->resultset('Testrun')->find($id);
203 1 50       3959 if ($testrun->testrun_scheduling) {
204 1 50       4116 return "Running testruns can not be deleted. Try freehost or wait till the testrun is finished."
205             if $testrun->testrun_scheduling->status eq 'running';
206 1 50       1095 if ($testrun->testrun_scheduling->requested_hosts->count) {
207 1         5080 foreach my $host ($testrun->testrun_scheduling->requested_hosts->all) {
208 1         2113 $host->delete();
209             }
210             }
211 1 50       14091 if ($testrun->testrun_scheduling->requested_features->count) {
212 1         6109 foreach my $feat ($testrun->testrun_scheduling->requested_features->all) {
213 1         2316 $feat->delete();
214             }
215             }
216             }
217              
218 1         13072 $testrun->delete();
219 1         46468 return 0;
220             }
221              
222              
223             sub rerun {
224 1     1 1 23784 my ($self, $id, $args) = @_;
225 1 50       3 my %args = %{$args || {}}; # copy
  1         9  
226 1         38 my $testrun = $self->schema->resultset('Testrun')->find( $id );
227 1         3860 return $testrun->rerun(\%args)->id;
228             }
229              
230              
231             sub pause {
232 0     0 1 0 my ($self, $id) = @_;
233 0         0 my $testrun = $self->schema->resultset('TestrunScheduling')->search
234             ({
235             testrun_id => $id,
236             status => 'schedule',
237             })->first;
238 0 0 0     0 if ($testrun and $testrun->testrun_id) {
239 0         0 return $testrun->update_content({status => 'prepare'});
240             }
241 0         0 return;
242             }
243              
244              
245             sub continue {
246 0     0 1 0 my ($self, $id) = @_;
247 0         0 my $testrun = $self->schema->resultset('TestrunScheduling')->search
248             ({
249             testrun_id => $id,
250             status => 'prepare',
251             })->first;
252 0 0 0     0 if ($testrun and $testrun->testrun_id) {
253 0         0 return $testrun->update_content({status => 'schedule'});
254             }
255 0         0 return;
256             }
257              
258              
259             sub cancel
260             {
261 2     2 1 135442 my ($self, $testrun_id, $comment) = @_;
262 2         7 my $msg = { 'state' => 'quit', };
263 2 100       8 if ( $comment ) {
264 1         4 $msg->{error} = $comment;
265             }
266 2 50       77 my $testrun_result = $self->schema->resultset('Testrun')->find( $testrun_id ) or die "No such testrun '$testrun_id'\n";
267 2 100 66     7750 if ($testrun_result->testrun_scheduling->status eq 'schedule' or
    50          
268             $testrun_result->testrun_scheduling->status eq 'prepare'
269             ) {
270 1         5109 $testrun_result->testrun_scheduling->status('finished');
271 1         287 $testrun_result->testrun_scheduling->update;
272             } elsif ( $testrun_result->testrun_scheduling->status eq 'running') {
273 1         5365 $self->schema->resultset('Message')->new({
274             testrun_id => $testrun_id,
275             message => $msg,
276             }
277             )->insert;
278             }
279 2         29184 return 0;
280             }
281              
282              
283              
284             sub status
285             {
286 1     1 1 4497 my ($self, $id) = @_;
287 1         2 my $result;
288 1         36 my $testrun = $self->schema->resultset('Testrun')->find($id);
289 1 50       3836 die "No testrun with id '$id'\n" if not $testrun;
290              
291 1         42 $result->{status} .= $testrun->testrun_scheduling->status; # the dot (.=) stringifies the enum object that the status actually contains
292 1         4825 $result->{success_ratio} = undef;
293              
294 1         27 my $reportgroup = $self->schema->resultset('ReportgroupTestrun')->search({testrun_id => $id});
295 1 50       566 if ($reportgroup->count > 0) {
296 0         0 $result->{reports} = [];
297 0         0 foreach my $group_element ($reportgroup->all) {
298 0         0 push @{$result->{reports}}, $group_element->report_id;
  0         0  
299 0 0       0 $result->{primaryreport} = $group_element->report_id if $group_element->primaryreport;
300             }
301             }
302              
303 1 50       3877 if ($result->{status} eq 'finished') {
304 0         0 my $stats = $self->schema->resultset('ReportgroupTestrunStats')->search({testrun_id => $id})->first;
305 0 0       0 return $result if not defined($stats);
306              
307 0         0 $result->{success_ratio} = $stats->success_ratio;
308 0 0       0 if ($stats->success_ratio < 100) {
309 0         0 $result->{status} = 'fail';
310             } else {
311 0         0 $result->{status} = 'pass';
312             }
313             }
314 1         4 return $result;
315             }
316              
317              
318              
319             1; # End of Tapper::Cmd::Testrun
320              
321             __END__
322              
323             =pod
324              
325             =encoding UTF-8
326              
327             =head1 NAME
328              
329             Tapper::Cmd::Testrun
330              
331             =head1 SYNOPSIS
332              
333             This project offers backend functions for all projects that manipulate
334             testruns or preconditions in the database. This module handles the testrun part.
335              
336             use Tapper::Cmd::Testrun;
337              
338             my $bar = Tapper::Cmd::Testrun->new();
339             $bar->add($testrun);
340             ...
341              
342             =head1 NAME
343              
344             Tapper::Cmd::Testrun - Backend functions for manipluation of testruns in the database
345              
346             =head1 FUNCTIONS
347              
348             =head2 find_matching_hosts
349              
350             =head2 create
351              
352             Create new testruns from a data structure that contains all information
353             including requested hosts and features. If the new testruns belong to a
354             test plan instance the function expects the id of this instance as
355             second parameter.
356              
357             @param hash ref - testrun description OR
358             string - YAML
359             @optparam instance - test plan instance id
360              
361             @return array - testrun ids
362              
363             @throws die()
364              
365             =head2 add
366              
367             Add a new testrun. Owner/owner_id and requested_hosts/requested_host_ids
368             allow to specify the associated value as id or string which will be converted
369             to the associated id. If both values are given the id is used and the string
370             is ignored. The function expects a hash reference with the following options:
371             -- optional --
372             * requested_host_ids - array of int
373             or
374             * requested_hosts - array of string
375              
376             * notes - string
377             * shortname - string
378             * topic - string
379             * date - DateTime
380             * instance - int
381              
382             * owner_id - int
383             or
384             * owner - string
385              
386             @param hash ref - options for new testrun
387              
388             @return success - testrun id)
389             @return error - exception
390              
391             @throws exception without class
392              
393             =head2 update
394              
395             Changes values of an existing testrun. The function expects a hash reference with
396             the following options (at least one should be given):
397              
398             * hostname - string
399             * notes - string
400             * shortname - string
401             * topic - string
402             * date - DateTime
403             * owner_id - int
404             or
405             * owner - string
406              
407             @param int - testrun id
408             @param hash ref - options for new testrun
409              
410             @return success - testrun id
411             @return error - undef
412              
413             =head2 del
414              
415             Delete a testrun with given id. Its named del instead of delete to
416             prevent confusion with the buildin delete function.
417              
418             @param int - testrun id
419              
420             @return success - 0
421             @return error - error string
422              
423             =head2 rerun
424              
425             Insert a new testrun into the database. All values not given are taken from
426             the existing testrun given as first argument.
427              
428             @param int - id of original testrun
429             @param hash ref - different values for new testrun
430              
431             @return success - testrun id
432             @return error - exception
433              
434             @throws exception without class
435              
436             =head2 pause
437              
438             Pause an existing testrun by setting its state to 'prepare'.
439              
440             @param int - id of original testrun
441              
442             @return success - testrun id
443             @return error - exception
444              
445             @throws exception without class
446              
447             =head2 continue
448              
449             Continue a paused testrun (status 'prepare') by setting its state back
450             to 'schedule'.
451              
452             @param int - id of original testrun
453              
454             @return success - testrun id
455             @return error - exception
456              
457             @throws exception without class
458              
459             =head2 cancel
460              
461             Stop a running testrun by sending the appropriate message to MCP. As
462             convenience to the user the function will also work on testruns that are
463             not running. In that case the return value contains a warning that the
464             caller should present to the user.
465              
466             @param int - testrun id
467             @optparam string - comment
468              
469             @return success - success string
470             @return error - error string
471              
472             @throws die()
473              
474             =head2 status
475              
476             Get information of one testrun.
477              
478             @param int - testrun id
479              
480             @return - hash ref -
481             * status - one of 'prepare', 'schedule', 'running', 'pass', 'fail'
482             * success_ratio - percentage of success
483              
484             @throws - die
485              
486             =head1 AUTHOR
487              
488             AMD OSRC Tapper Team, C<< <tapper at amd64.org> >>
489              
490             =head1 COPYRIGHT & LICENSE
491              
492             Copyright 2012 AMD OSRC Tapper Team, all rights reserved.
493              
494             This program is released under the following license: freebsd
495              
496             =head1 AUTHORS
497              
498             =over 4
499              
500             =item *
501              
502             AMD OSRC Tapper Team <tapper@amd64.org>
503              
504             =item *
505              
506             Tapper Team <tapper-ops@amazon.com>
507              
508             =back
509              
510             =head1 COPYRIGHT AND LICENSE
511              
512             This software is Copyright (c) 2020 by Advanced Micro Devices, Inc..
513              
514             This is free software, licensed under:
515              
516             The (two-clause) FreeBSD License
517              
518             =cut