line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Tapper::CLI::Testrun; |
2
|
|
|
|
|
|
|
our $AUTHORITY = 'cpan:TAPPER'; |
3
|
|
|
|
|
|
|
$Tapper::CLI::Testrun::VERSION = '5.0.6'; |
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
968
|
use 5.010; |
|
1
|
|
|
|
|
3
|
|
6
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
24
|
|
7
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
22
|
|
8
|
1
|
|
|
1
|
|
5
|
use feature qw/ say /; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
133
|
|
9
|
1
|
|
|
1
|
|
7
|
use English qw/ -no_match_vars /; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
15
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# list limit default value |
12
|
|
|
|
|
|
|
my $i_limit_default = 50; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub b_print_single_testrun { |
16
|
|
|
|
|
|
|
|
17
|
0
|
|
|
0
|
1
|
|
my ( $or_testrun ) = @_; |
18
|
|
|
|
|
|
|
|
19
|
0
|
|
|
|
|
|
print "\n"; |
20
|
0
|
|
|
|
|
|
printf "%17s: %s\n", 'Id' , $or_testrun->id; |
21
|
0
|
|
|
|
|
|
printf "%17s: %s\n", 'Topic', $or_testrun->topic_name; |
22
|
|
|
|
|
|
|
|
23
|
0
|
0
|
|
|
|
|
if ( $or_testrun->shortname ) { |
24
|
0
|
|
|
|
|
|
printf "%17s: %s\n", 'Shortname', $or_testrun->shortname; |
25
|
|
|
|
|
|
|
} |
26
|
0
|
0
|
|
|
|
|
if ( $or_testrun->testrun_scheduling ) { |
27
|
|
|
|
|
|
|
|
28
|
0
|
|
|
|
|
|
require Tapper::Cmd::Testrun; |
29
|
0
|
|
|
|
|
|
my $or_cmd = Tapper::Cmd::Testrun->new(); |
30
|
0
|
|
|
|
|
|
my $hr_testrun_details = $or_cmd->status($or_testrun->id); |
31
|
0
|
|
|
|
|
|
printf "%17s: %s\n", 'State', $hr_testrun_details->{status}; |
32
|
0
|
|
|
|
|
|
printf "%17s: %s\n", 'Queue', $or_testrun->testrun_scheduling->queue->name; |
33
|
|
|
|
|
|
|
|
34
|
0
|
0
|
|
|
|
|
if ( $or_testrun->testrun_scheduling->status eq 'schedule' ) { |
35
|
0
|
0
|
|
|
|
|
if ( $or_testrun->testrun_scheduling->requested_hosts->count ) { |
36
|
0
|
|
|
|
|
|
printf "%17s: %s\n", q#Requested Host's#, join ",", $or_testrun->testrun_scheduling->requested_hosts->related_resultset('host')->get_column('name')->all; |
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
} |
39
|
|
|
|
|
|
|
else { |
40
|
0
|
0
|
0
|
|
|
|
if ( |
41
|
|
|
|
|
|
|
$or_testrun->testrun_scheduling->host && |
42
|
|
|
|
|
|
|
$or_testrun->testrun_scheduling->host->name |
43
|
|
|
|
|
|
|
) { |
44
|
0
|
|
|
|
|
|
printf "%17s: %s\n", 'Used Host', $or_testrun->testrun_scheduling->host->name; |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
0
|
0
|
|
|
|
|
printf "%17s: %s\n", 'Auto rerun', $or_testrun->testrun_scheduling->auto_rerun ? 'yes' : 'no'; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
else { |
52
|
0
|
|
|
|
|
|
printf "%17s: %s\n", q##, 'Old testrun without scheduling information'; |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
0
|
0
|
|
|
|
|
if ( $or_testrun->notes ) { |
56
|
0
|
|
|
|
|
|
printf "%17s: %s\n", 'Notes', $or_testrun->notes; |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
0
|
|
|
|
|
|
printf "%17s: ", q#Precondition Id's#; |
60
|
0
|
0
|
|
|
|
|
if ( my @a_preconditions = $or_testrun->ordered_preconditions ) { |
61
|
0
|
|
|
|
|
|
say join ", ", map {$_->id} @a_preconditions; |
|
0
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
} |
63
|
|
|
|
|
|
|
else { |
64
|
0
|
|
|
|
|
|
say 'None'; |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
|
67
|
0
|
|
|
|
|
|
return 1; |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub b_print_testruns { |
73
|
|
|
|
|
|
|
|
74
|
0
|
|
|
0
|
1
|
|
my ( $or_testruns ) = @_; |
75
|
|
|
|
|
|
|
|
76
|
0
|
0
|
|
|
|
|
if ( $or_testruns->isa('DBIx::Class::ResultSet') ) { |
77
|
0
|
|
|
|
|
|
for my $or_testrun ( $or_testruns->all ) { |
78
|
0
|
|
|
|
|
|
b_print_single_testrun( $or_testrun ); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
else { |
82
|
0
|
|
|
|
|
|
b_print_single_testrun( $or_testruns ); |
83
|
|
|
|
|
|
|
} |
84
|
0
|
|
|
|
|
|
print "\n"; |
85
|
|
|
|
|
|
|
|
86
|
0
|
|
|
|
|
|
return 1; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub ar_get_list_testrun_parameters { |
92
|
|
|
|
|
|
|
return [ |
93
|
0
|
|
|
0
|
1
|
|
[ 'id|i=i@' , 'list particular testruns', 'Can be given multiple times.', ], |
94
|
|
|
|
|
|
|
[ 'finished|f', 'list finished testruns, OR combined with other state filters', ], |
95
|
|
|
|
|
|
|
[ 'running|r' , 'list running testruns, OR combined with other state filters', ], |
96
|
|
|
|
|
|
|
[ 'schedule|s', 'list scheduled testruns, OR combined with other state filters', ], |
97
|
|
|
|
|
|
|
[ 'prepare|p' , 'list testruns not yet in any scheduling queue, OR combined with other state filters', ], |
98
|
|
|
|
|
|
|
[ 'queue|q=s@', 'list testruns assigned to this queue, OR combined with other queues, AND combined with other filters', ], |
99
|
|
|
|
|
|
|
[ 'host|h=s@' , 'list testruns assigned to this queue, OR combined with other hosts, AND combined with other filters', ], |
100
|
|
|
|
|
|
|
[ 'limit|l=i' , "limit the number of testruns (default = $i_limit_default). A value smaller than 1 deactivates the limit.", ], |
101
|
|
|
|
|
|
|
[ 'verbose|v' , 'print all output, without only print ids', ], |
102
|
|
|
|
|
|
|
[ 'help|?' , 'Print this help message and exit.', ], |
103
|
|
|
|
|
|
|
]; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
sub ar_get_rerun_testrun_parameters { |
108
|
|
|
|
|
|
|
return [ |
109
|
0
|
|
|
0
|
1
|
|
[ 'id|i=i@' , 'rerun particular testruns', 'Can be given multiple times.', ], |
110
|
|
|
|
|
|
|
[ 'notes|n=s' , 'add a description for new testruns', ], |
111
|
|
|
|
|
|
|
[ 'verbose|v' , 'print all output, without only print ids', ], |
112
|
|
|
|
|
|
|
[ 'help|?' , 'Print this help message and exit.', ], |
113
|
|
|
|
|
|
|
]; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub ar_get_delete_testrun_parameters { |
118
|
|
|
|
|
|
|
return [ |
119
|
0
|
|
|
0
|
1
|
|
[ 'id|i=i@' , 'delete particular testruns', 'Can be given multiple times.', ], |
120
|
|
|
|
|
|
|
[ 'force|f' , 'really execute the command', ], |
121
|
|
|
|
|
|
|
[ 'verbose|v', 'print all output, without only print ids', ], |
122
|
|
|
|
|
|
|
[ 'help|?' , 'Print this help message and exit.', ], |
123
|
|
|
|
|
|
|
]; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
sub ar_get_create_testrun_parameters { |
128
|
|
|
|
|
|
|
return [ |
129
|
|
|
|
|
|
|
[ |
130
|
0
|
|
|
0
|
1
|
|
'macroprecond|m=s', |
131
|
|
|
|
|
|
|
'use this macro precondition file', |
132
|
|
|
|
|
|
|
],[ |
133
|
|
|
|
|
|
|
'precondition|p=i@', |
134
|
|
|
|
|
|
|
'assigned precondition ids', |
135
|
|
|
|
|
|
|
],[ |
136
|
|
|
|
|
|
|
'owner|o=s', |
137
|
|
|
|
|
|
|
'default=$USER; user login name', |
138
|
|
|
|
|
|
|
],[ |
139
|
|
|
|
|
|
|
'topic|t=s', |
140
|
|
|
|
|
|
|
'default=Misc; one of: Kernel, Xen, KVM, Hardware, Distribution, Benchmark, Software, Misc', |
141
|
|
|
|
|
|
|
],[ |
142
|
|
|
|
|
|
|
'queue|q=s', |
143
|
|
|
|
|
|
|
'default=AdHoc', |
144
|
|
|
|
|
|
|
],[ |
145
|
|
|
|
|
|
|
'notes|n=s', |
146
|
|
|
|
|
|
|
'notes', |
147
|
|
|
|
|
|
|
],[ |
148
|
|
|
|
|
|
|
'rerun_on_error=i', |
149
|
|
|
|
|
|
|
'retry this testrun this many times if an error occurs', |
150
|
|
|
|
|
|
|
],[ |
151
|
|
|
|
|
|
|
'shortname|s=s', |
152
|
|
|
|
|
|
|
'shortname', |
153
|
|
|
|
|
|
|
],[ |
154
|
|
|
|
|
|
|
'earliest|e=s', |
155
|
|
|
|
|
|
|
q#default=now; don't start testrun before this time (format: YYYY-MM-DD hh:mm:ss or now)#, |
156
|
|
|
|
|
|
|
],[ |
157
|
|
|
|
|
|
|
'requested_host|rh=s@', |
158
|
|
|
|
|
|
|
'String; name one possible host for this testrequest;', |
159
|
|
|
|
|
|
|
'multiple requested hosts are OR evaluated, i.e. each is appropriate', |
160
|
|
|
|
|
|
|
],[ |
161
|
|
|
|
|
|
|
'requested_feature|rf=s@', |
162
|
|
|
|
|
|
|
'description of one requested feature of a matching host for this testrequest;', |
163
|
|
|
|
|
|
|
'multiple requested features are AND evaluated, i.e. each must fit;', |
164
|
|
|
|
|
|
|
'not evaluated if a matching requested host is found already', |
165
|
|
|
|
|
|
|
],[ |
166
|
|
|
|
|
|
|
'notify:s', |
167
|
|
|
|
|
|
|
q#create a notification for when the testrun is finished, possibly with filter for 'fail' or 'success'#, |
168
|
|
|
|
|
|
|
],[ |
169
|
|
|
|
|
|
|
'wait_after_tests', |
170
|
|
|
|
|
|
|
'default=0; wait after testrun for human investigation', |
171
|
|
|
|
|
|
|
],[ |
172
|
|
|
|
|
|
|
'verbose|v', |
173
|
|
|
|
|
|
|
'some more informational output', |
174
|
|
|
|
|
|
|
],[ |
175
|
|
|
|
|
|
|
'dryrun', |
176
|
|
|
|
|
|
|
'default=0; only print the preconditions to stdout and then exit', |
177
|
|
|
|
|
|
|
],[ |
178
|
|
|
|
|
|
|
'auto_rerun', |
179
|
|
|
|
|
|
|
'default=0; put this testrun into db again when it is chosen by scheduler', |
180
|
|
|
|
|
|
|
],[ |
181
|
|
|
|
|
|
|
'priority', |
182
|
|
|
|
|
|
|
'This is a very important testrun that should bypass scheduling and not wait for others', |
183
|
|
|
|
|
|
|
],[ |
184
|
|
|
|
|
|
|
'D=s%', |
185
|
|
|
|
|
|
|
'Define a key=value pair used in macro preconditions', |
186
|
|
|
|
|
|
|
],[ |
187
|
|
|
|
|
|
|
'help|?', |
188
|
|
|
|
|
|
|
'Print this help message and exit', |
189
|
|
|
|
|
|
|
], |
190
|
|
|
|
|
|
|
]; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
sub create_macro_preconditions { |
195
|
|
|
|
|
|
|
|
196
|
0
|
|
|
0
|
1
|
|
my ( $hr_options ) = @_; |
197
|
|
|
|
|
|
|
|
198
|
0
|
|
|
|
|
|
my $hr_d = $hr_options->{D}; # options are auto-down-cased |
199
|
0
|
|
|
|
|
|
my $b_dryrun = $hr_options->{dryrun}; |
200
|
0
|
|
|
|
|
|
my $s_ttapplied = $hr_options->{macroprecond_evaluated}; |
201
|
|
|
|
|
|
|
|
202
|
0
|
0
|
|
|
|
|
if ($b_dryrun) { |
203
|
0
|
|
|
|
|
|
print $s_ttapplied; |
204
|
0
|
|
|
|
|
|
exit 0; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
|
require Tapper::Cmd::Precondition; |
208
|
0
|
|
|
|
|
|
return Tapper::Cmd::Precondition->new->add( $s_ttapplied ); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub s_create_testrun_parameter_check { |
214
|
|
|
|
|
|
|
|
215
|
0
|
|
|
0
|
1
|
|
my ( $hr_options ) = @_; |
216
|
|
|
|
|
|
|
|
217
|
0
|
0
|
0
|
|
|
|
if ( !$hr_options->{precondition} && !$hr_options->{macroprecond} ) { |
218
|
0
|
|
|
|
|
|
return q#At least one of "precondition" or "macroprecond" is required#; |
219
|
|
|
|
|
|
|
} |
220
|
0
|
0
|
0
|
|
|
|
if ( exists $hr_options->{rerun_on_error} && $hr_options->{rerun_on_error} !~ /^\d+$/ ) { |
221
|
0
|
|
|
|
|
|
return "value for rerun_on_error ($hr_options->{rerun_on_error}) is not an integer value"; |
222
|
|
|
|
|
|
|
} |
223
|
0
|
0
|
|
|
|
|
if ( $hr_options->{earliest} ) { |
224
|
0
|
|
|
|
|
|
require DateTime::Format::Natural; |
225
|
0
|
|
|
|
|
|
my $or_parser = DateTime::Format::Natural->new; |
226
|
0
|
|
|
|
|
|
$hr_options->{earliest} = $or_parser->parse_datetime( $hr_options->{earliest} ); |
227
|
0
|
0
|
|
|
|
|
if ( $or_parser->success ) { |
228
|
0
|
0
|
|
|
|
|
if ( $hr_options->{verbose} ) { |
229
|
0
|
|
|
|
|
|
say $hr_options->{earliest}->strftime('%d.%m.%Y %T'); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
else { |
233
|
0
|
|
|
|
|
|
return $or_parser->error; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
|
require Tapper::Model; |
238
|
0
|
0
|
|
|
|
|
if ( $hr_options->{requested_host} ) { |
239
|
0
|
|
|
|
|
|
for my $i_counter ( 0..$#{$hr_options->{requested_host}} ) { |
|
0
|
|
|
|
|
|
|
240
|
0
|
0
|
|
|
|
|
if ( |
241
|
|
|
|
|
|
|
my $or_host = |
242
|
|
|
|
|
|
|
Tapper::Model::model('TestrunDB') |
243
|
|
|
|
|
|
|
->resultset('Host') |
244
|
|
|
|
|
|
|
->search( |
245
|
|
|
|
|
|
|
{ name => $hr_options->{requested_host}[$i_counter] }, |
246
|
|
|
|
|
|
|
{ rows => 1 }, |
247
|
|
|
|
|
|
|
) |
248
|
|
|
|
|
|
|
->first |
249
|
|
|
|
|
|
|
) { |
250
|
0
|
|
|
|
|
|
$hr_options->{requested_host}[$i_counter] = $or_host->id; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
else { |
253
|
0
|
|
|
|
|
|
die "Host '$hr_options->{requested_host}[$i_counter]' does not exist\n"; |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
} |
257
|
0
|
0
|
|
|
|
|
if ( $hr_options->{notify} ) { |
258
|
0
|
|
|
|
|
|
$hr_options->{notify} = lc $hr_options->{notify}; |
259
|
0
|
0
|
|
|
|
|
if (! grep { $hr_options->{notify} eq $_ } qw/ pass fail / ) { |
|
0
|
|
|
|
|
|
|
260
|
0
|
|
|
|
|
|
die "invalid value for 'notify': valid values 'pass', 'fail'\n"; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
0
|
|
|
|
|
|
require File::Slurp; |
265
|
0
|
0
|
|
|
|
|
if ( $hr_options->{macroprecond} ) { |
266
|
0
|
0
|
|
|
|
|
if ( -e $hr_options->{macroprecond} ) { |
267
|
|
|
|
|
|
|
|
268
|
0
|
|
|
|
|
|
require Tapper::Cmd; |
269
|
0
|
|
|
|
|
|
my $or_cmd = Tapper::Cmd->new; |
270
|
0
|
|
|
|
|
|
my $s_mpc_file = $hr_options->{macroprecond}; |
271
|
0
|
|
|
|
|
|
my $hr_d = $hr_options->{D}; |
272
|
|
|
|
|
|
|
|
273
|
0
|
|
|
|
|
|
$hr_options->{macroprecond_evaluated} = $or_cmd->apply_macro($s_mpc_file, $hr_d); |
274
|
0
|
0
|
|
|
|
|
if ( (my $s_required) = $hr_options->{macroprecond_evaluated} =~/^# (?:tapper[_-])?mandatory[_-]fields:\s*(.+)/m ) { |
275
|
0
|
|
|
|
|
|
my $re_delim = qr/,+\s*/; |
276
|
0
|
|
|
|
|
|
foreach my $s_field ( split $re_delim, $s_required ) { |
277
|
0
|
|
|
|
|
|
$s_field =~ s/\s+//g; |
278
|
0
|
|
|
|
|
|
my ( $s_name, undef ) = split /\./, $s_field; |
279
|
0
|
0
|
|
|
|
|
if (! $hr_options->{D}{$s_name} ) { |
280
|
0
|
|
|
|
|
|
die "Expected macro field '$s_name' missing.\n"; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
0
|
|
|
|
|
|
return; |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
sub b_create_testrun { |
295
|
|
|
|
|
|
|
|
296
|
0
|
|
|
0
|
1
|
|
my ( $or_app_rad ) = @_; |
297
|
|
|
|
|
|
|
|
298
|
0
|
|
|
|
|
|
my $ar_parameters = ar_get_create_testrun_parameters(); |
299
|
0
|
|
|
|
|
|
$or_app_rad->getopt( map { $_->[0] } @{$ar_parameters} ); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
|
my $hr_options = $or_app_rad->options; |
301
|
|
|
|
|
|
|
|
302
|
0
|
0
|
|
|
|
|
if ( $hr_options->{help} ) { |
303
|
0
|
|
|
|
|
|
say {*STDERR} "Usage: $PROGRAM_NAME testrun-new [options]"; |
|
0
|
|
|
|
|
|
|
304
|
0
|
|
|
|
|
|
require Tapper::CLI::Base; |
305
|
0
|
|
|
|
|
|
Tapper::CLI::Base::b_print_help( $ar_parameters ); |
306
|
0
|
|
|
|
|
|
return; |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
|
309
|
0
|
0
|
|
|
|
|
if ( my $s_error = s_create_testrun_parameter_check( $hr_options ) ) { |
310
|
0
|
|
|
|
|
|
die 'error: ' . $s_error . "\n"; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
0
|
|
|
|
|
|
my @a_ids; |
314
|
0
|
0
|
|
|
|
|
if ( $hr_options->{macroprecond} ) { |
315
|
0
|
|
|
|
|
|
@a_ids = create_macro_preconditions( $hr_options ); |
316
|
|
|
|
|
|
|
} |
317
|
0
|
0
|
|
|
|
|
if ( $hr_options->{precondition} ) { |
318
|
0
|
|
|
|
|
|
push @a_ids, @{$hr_options->{precondition}}; |
|
0
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
0
|
0
|
|
|
|
|
if (! @a_ids ) { |
322
|
0
|
|
|
|
|
|
die "error: No valid preconditions given\n"; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
|
325
|
0
|
|
|
|
|
|
require DateTime; |
326
|
|
|
|
|
|
|
my $hr_testrun = { |
327
|
|
|
|
|
|
|
wait_after_tests => $hr_options->{wait_after_tests}, |
328
|
|
|
|
|
|
|
priority => $hr_options->{priority} || 0, |
329
|
|
|
|
|
|
|
auto_rerun => $hr_options->{auto_rerun} || 0, |
330
|
|
|
|
|
|
|
earliest => $hr_options->{earliest} || DateTime->now, |
331
|
|
|
|
|
|
|
notes => $hr_options->{notes} || q##, |
332
|
|
|
|
|
|
|
owner => $hr_options->{owner} || $ENV{USER}, |
333
|
|
|
|
|
|
|
queue => $hr_options->{queue} || 'AdHoc', |
334
|
|
|
|
|
|
|
rerun_on_error => $hr_options->{rerun_on_error} ? int( $hr_options->{rerun_on_error} ) || 0 : 0, |
335
|
|
|
|
|
|
|
shortname => $hr_options->{shortname} || q##, |
336
|
0
|
0
|
0
|
|
|
|
topic => $hr_options->{topic} || 'Misc', |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
337
|
|
|
|
|
|
|
}; |
338
|
|
|
|
|
|
|
|
339
|
0
|
0
|
|
|
|
|
if ( exists $hr_options->{notify} ) { |
340
|
0
|
|
|
|
|
|
$hr_testrun->{notify} = $hr_options->{notify}; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
0
|
|
|
|
|
|
require Tapper::Cmd::Testrun; |
344
|
0
|
|
|
|
|
|
my $or_cmd = Tapper::Cmd::Testrun->new(); |
345
|
0
|
|
|
|
|
|
my ( $i_testrun_id, $s_error ) = $or_cmd->add( $hr_testrun ); |
346
|
|
|
|
|
|
|
|
347
|
0
|
0
|
|
|
|
|
if ( $i_testrun_id ) { |
348
|
0
|
0
|
|
|
|
|
if ( $s_error ) { |
349
|
0
|
|
|
|
|
|
say {*STDERR} "warning: $s_error"; |
|
0
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
else { |
353
|
0
|
0
|
|
|
|
|
if ( $s_error ) { |
354
|
0
|
|
|
|
|
|
die "error: Can't create new testrun\n$s_error\n"; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
else { |
357
|
0
|
|
|
|
|
|
die "error: Can't create new testrun because of an unknown error\n"; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
} |
360
|
|
|
|
|
|
|
|
361
|
0
|
|
|
|
|
|
require Tapper::Model; |
362
|
0
|
|
|
|
|
|
my $or_testrun_search = |
363
|
|
|
|
|
|
|
Tapper::Model::model('TestrunDB') |
364
|
|
|
|
|
|
|
->resultset('Testrun') |
365
|
|
|
|
|
|
|
->find( $i_testrun_id ) |
366
|
|
|
|
|
|
|
; |
367
|
|
|
|
|
|
|
|
368
|
0
|
0
|
|
|
|
|
if ( my $retval = $or_cmd->assign_preconditions( $i_testrun_id, @a_ids ) ) { |
369
|
0
|
|
|
|
|
|
$or_testrun_search->delete(); |
370
|
0
|
|
|
|
|
|
die $retval . "\n"; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
0
|
|
|
|
|
|
require Tapper::Cmd::Requested; |
374
|
|
|
|
|
|
|
|
375
|
0
|
|
|
|
|
|
my $or_cmd_req; |
376
|
0
|
0
|
|
|
|
|
if ( $hr_options->{requested_host} ) { |
377
|
0
|
|
|
|
|
|
$or_cmd_req = Tapper::Cmd::Requested->new; |
378
|
0
|
|
|
|
|
|
foreach my $s_host ( @{$hr_options->{requested_host}} ) { |
|
0
|
|
|
|
|
|
|
379
|
0
|
0
|
|
|
|
|
if (! $or_cmd_req->add_host( $i_testrun_id, $s_host, ) ) { |
380
|
0
|
|
|
|
|
|
die "error: adding host failed\n"; |
381
|
|
|
|
|
|
|
} |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
0
|
0
|
|
|
|
|
if ( $hr_options->{requested_feature} ) { |
386
|
0
|
|
0
|
|
|
|
$or_cmd_req ||= Tapper::Cmd::Requested->new; |
387
|
0
|
|
|
|
|
|
foreach my $s_feature ( @{$hr_options->{requested_feature}} ) { |
|
0
|
|
|
|
|
|
|
388
|
0
|
0
|
|
|
|
|
if (! $or_cmd_req->add_feature( $i_testrun_id, $s_feature ) ) { |
389
|
0
|
|
|
|
|
|
die "error: adding feature failed\n"; |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
} |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
|
394
|
0
|
|
|
|
|
|
require DateTime; |
395
|
0
|
|
|
|
|
|
$or_testrun_search->testrun_scheduling->updated_at( DateTime->now->strftime('%F %T') ); |
396
|
0
|
|
|
|
|
|
$or_testrun_search->testrun_scheduling->status('schedule'); |
397
|
0
|
|
|
|
|
|
$or_testrun_search->testrun_scheduling->update; |
398
|
|
|
|
|
|
|
|
399
|
0
|
0
|
|
|
|
|
if ( $hr_options->{verbose} ) { |
400
|
0
|
|
|
|
|
|
say $or_testrun_search->to_string; |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
else { |
403
|
0
|
0
|
|
|
|
|
if ( $ENV{TAPPER_WITH_WEB} ) { |
404
|
0
|
|
|
|
|
|
my $s_webserver = Tapper::Config->subconfig->{webserver}; |
405
|
0
|
|
|
|
|
|
say "http://$s_webserver/tapper/testrun/id/$i_testrun_id"; |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
else { |
408
|
0
|
|
|
|
|
|
say $i_testrun_id; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
0
|
|
|
|
|
|
return; |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
sub b_delete { |
418
|
|
|
|
|
|
|
|
419
|
0
|
|
|
0
|
1
|
|
my ( $or_app_rad ) = @_; |
420
|
|
|
|
|
|
|
|
421
|
0
|
|
|
|
|
|
my $ar_parameters = ar_get_delete_testrun_parameters(); |
422
|
0
|
|
|
|
|
|
$or_app_rad->getopt( map { $_->[0] } @{$ar_parameters} ); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
423
|
0
|
|
|
|
|
|
my $hr_options = $or_app_rad->options; |
424
|
|
|
|
|
|
|
|
425
|
0
|
0
|
|
|
|
|
if ( $hr_options->{help} ) { |
426
|
0
|
|
|
|
|
|
say {*STDERR} "Usage: $PROGRAM_NAME testrun-delete [options]"; |
|
0
|
|
|
|
|
|
|
427
|
0
|
|
|
|
|
|
require Tapper::CLI::Base; |
428
|
0
|
|
|
|
|
|
Tapper::CLI::Base::b_print_help( $ar_parameters ); |
429
|
0
|
|
|
|
|
|
return; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
0
|
0
|
|
|
|
|
if (! $hr_options->{force} ) { |
433
|
0
|
|
|
|
|
|
say {*STDERR} "info: Skip all testruns. Use --force."; |
|
0
|
|
|
|
|
|
|
434
|
0
|
|
|
|
|
|
return; |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
0
|
|
|
|
|
|
require Tapper::Cmd::Testrun; |
438
|
0
|
|
|
|
|
|
my $or_cmd = Tapper::Cmd::Testrun->new(); |
439
|
0
|
|
|
|
|
|
for my $i_testrun_id ( @{$hr_options->{id}} ){ |
|
0
|
|
|
|
|
|
|
440
|
0
|
0
|
|
|
|
|
if ( my $s_error = $or_cmd->del( $i_testrun_id ) ) { |
441
|
0
|
|
|
|
|
|
die "error: Can not delete testrun $i_testrun_id: $s_error\n"; |
442
|
|
|
|
|
|
|
} |
443
|
0
|
0
|
|
|
|
|
if ( $hr_options->{verbose} ) { |
444
|
0
|
|
|
|
|
|
say "info: deleted testrun $i_testrun_id\n"; |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
0
|
|
|
|
|
|
return; |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
sub b_rerun { |
454
|
|
|
|
|
|
|
|
455
|
0
|
|
|
0
|
1
|
|
my ( $or_app_rad ) = @_; |
456
|
|
|
|
|
|
|
|
457
|
0
|
|
|
|
|
|
my $ar_parameters = ar_get_rerun_testrun_parameters(); |
458
|
0
|
|
|
|
|
|
$or_app_rad->getopt( map { $_->[0] } @{$ar_parameters} ); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
459
|
0
|
|
|
|
|
|
my $hr_options = $or_app_rad->options; |
460
|
|
|
|
|
|
|
|
461
|
0
|
0
|
|
|
|
|
if ( $hr_options->{help} ) { |
462
|
0
|
|
|
|
|
|
say {*STDERR} "Usage: $PROGRAM_NAME testrun-rerun [options]"; |
|
0
|
|
|
|
|
|
|
463
|
0
|
|
|
|
|
|
require Tapper::CLI::Base; |
464
|
0
|
|
|
|
|
|
Tapper::CLI::Base::b_print_help( $ar_parameters ); |
465
|
0
|
|
|
|
|
|
return; |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
|
468
|
0
|
|
|
|
|
|
require Tapper::Cmd::Testrun; |
469
|
0
|
|
|
|
|
|
my $or_testrun = Tapper::Cmd::Testrun->new(); |
470
|
|
|
|
|
|
|
|
471
|
0
|
0
|
|
|
|
|
if ( my $ar_testrun_ids = $hr_options->{id} ) { |
472
|
0
|
|
|
|
|
|
for my $i_testrun_id ( @{$ar_testrun_ids} ) { |
|
0
|
|
|
|
|
|
|
473
|
0
|
0
|
|
|
|
|
if ( my $i_new_testrun_id = $or_testrun->rerun( $i_testrun_id, $hr_options ) ) { |
474
|
0
|
0
|
|
|
|
|
if ( $hr_options->{verbose} ) { |
475
|
0
|
|
|
|
|
|
my $or_new_testrun = Tapper::Model::model->resultset('TestRun')->find($i_new_testrun_id); |
476
|
0
|
|
|
|
|
|
b_print_testruns( $or_new_testrun ); |
477
|
0
|
|
|
|
|
|
say 'info: original id: ', $i_testrun_id; |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
else { |
480
|
0
|
|
|
|
|
|
say $i_new_testrun_id; |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
else { |
484
|
0
|
|
|
|
|
|
die "error: Can't restart testrun $i_testrun_id\n"; |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
} |
487
|
0
|
|
|
|
|
|
return; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
else { |
490
|
0
|
|
|
|
|
|
die "error: missing required parameter id\n"; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
sub ar_get_queue_ids { |
497
|
|
|
|
|
|
|
|
498
|
0
|
|
|
0
|
1
|
|
my ( $or_schema, $ar_queue_names ) = @_; |
499
|
|
|
|
|
|
|
|
500
|
0
|
|
|
|
|
|
my @a_check_queues; |
501
|
0
|
|
|
|
|
|
foreach my $s_queue ( @{$ar_queue_names} ) { |
|
0
|
|
|
|
|
|
|
502
|
0
|
0
|
|
|
|
|
if ( |
503
|
|
|
|
|
|
|
my $or_queue_rs = |
504
|
|
|
|
|
|
|
$or_schema |
505
|
|
|
|
|
|
|
->resultset('Queue') |
506
|
|
|
|
|
|
|
->search({ |
507
|
|
|
|
|
|
|
name => $s_queue, |
508
|
|
|
|
|
|
|
},{ |
509
|
|
|
|
|
|
|
'select' => [ 'id' ], |
510
|
|
|
|
|
|
|
}) |
511
|
|
|
|
|
|
|
) { |
512
|
0
|
|
|
|
|
|
push @a_check_queues, $or_queue_rs->get_column('id')->all |
513
|
|
|
|
|
|
|
} |
514
|
|
|
|
|
|
|
else { |
515
|
0
|
|
|
|
|
|
die "error: No such queue: $s_queue\n"; |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
|
519
|
0
|
0
|
|
|
|
|
if ( @a_check_queues ) { |
520
|
0
|
|
|
|
|
|
return @a_check_queues; |
521
|
|
|
|
|
|
|
} |
522
|
0
|
|
|
|
|
|
return; |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
} |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
sub ar_get_host_ids { |
528
|
|
|
|
|
|
|
|
529
|
0
|
|
|
0
|
1
|
|
my ( $or_schema, $ar_host_names ) = @_; |
530
|
|
|
|
|
|
|
|
531
|
0
|
|
|
|
|
|
my @a_check_hosts = (); |
532
|
0
|
|
|
|
|
|
foreach my $s_host ( @{$ar_host_names} ) { |
|
0
|
|
|
|
|
|
|
533
|
0
|
0
|
|
|
|
|
if ( |
534
|
|
|
|
|
|
|
my $or_host_rs = |
535
|
|
|
|
|
|
|
$or_schema |
536
|
|
|
|
|
|
|
->resultset('Host') |
537
|
|
|
|
|
|
|
->search({ |
538
|
|
|
|
|
|
|
name => $s_host |
539
|
|
|
|
|
|
|
},{ |
540
|
|
|
|
|
|
|
'select' => [ 'id' ], |
541
|
|
|
|
|
|
|
}) |
542
|
|
|
|
|
|
|
) { |
543
|
0
|
|
|
|
|
|
push @a_check_hosts, $or_host_rs->get_column('id')->all; |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
else { |
546
|
0
|
|
|
|
|
|
die "error: No such host: $s_host\n"; |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
} |
549
|
|
|
|
|
|
|
|
550
|
0
|
0
|
|
|
|
|
if ( @a_check_hosts ) { |
551
|
0
|
|
|
|
|
|
return @a_check_hosts; |
552
|
|
|
|
|
|
|
} |
553
|
0
|
|
|
|
|
|
return; |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
} |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
sub b_list_testrun { |
559
|
|
|
|
|
|
|
|
560
|
0
|
|
|
0
|
1
|
|
my ( $or_app_rad ) = @_; |
561
|
|
|
|
|
|
|
|
562
|
0
|
|
|
|
|
|
my $or_querylog; |
563
|
|
|
|
|
|
|
|
564
|
0
|
|
|
|
|
|
require Tapper::Model; |
565
|
0
|
|
|
|
|
|
my $or_schema = Tapper::Model::model('TestrunDB'); |
566
|
|
|
|
|
|
|
|
567
|
0
|
0
|
|
|
|
|
if ( $ENV{DEBUG} ) { |
568
|
0
|
|
|
|
|
|
require DBIx::Class::QueryLog; |
569
|
0
|
|
|
|
|
|
$or_querylog = DBIx::Class::QueryLog->new(); |
570
|
0
|
|
|
|
|
|
$or_schema->storage->debugobj( $or_querylog ); |
571
|
0
|
|
|
|
|
|
$or_schema->storage->debug( 1 ); |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
0
|
|
|
|
|
|
my $ar_parameters = ar_get_list_testrun_parameters(); |
575
|
0
|
|
|
|
|
|
$or_app_rad->getopt( map { $_->[0] } @{$ar_parameters} ); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
576
|
0
|
|
|
|
|
|
my %h_options = %{$or_app_rad->options}; |
|
0
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
|
578
|
0
|
0
|
|
|
|
|
if ( $h_options{help} ) { |
579
|
0
|
|
|
|
|
|
say {*STDERR} "Usage: $PROGRAM_NAME testrun-rerun [options]"; |
|
0
|
|
|
|
|
|
|
580
|
0
|
|
|
|
|
|
require Tapper::CLI::Base; |
581
|
0
|
|
|
|
|
|
Tapper::CLI::Base::b_print_help( $ar_parameters ); |
582
|
0
|
|
|
|
|
|
return; |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
my @a_given_options = grep { |
586
|
0
|
|
|
|
|
|
$h_options{$_} |
|
0
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
} qw/ finished running schedule prepare queue host /; |
588
|
|
|
|
|
|
|
|
589
|
0
|
|
|
|
|
|
my $hr_search = {}; |
590
|
0
|
|
|
|
|
|
my @a_check_queues = (); |
591
|
|
|
|
|
|
|
|
592
|
0
|
0
|
|
|
|
|
if ( my $i_testrun_id = $h_options{id} ) { |
|
|
0
|
|
|
|
|
|
593
|
|
|
|
|
|
|
|
594
|
0
|
0
|
|
|
|
|
if ( @a_given_options ) { |
595
|
0
|
|
|
|
|
|
print {*STDERR} "error: other filters doesn't make sense with filter 'id'\n"; |
|
0
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
# filter 'id' doesn't make sense without verbose |
599
|
0
|
|
|
|
|
|
$h_options{verbose} = 1; |
600
|
|
|
|
|
|
|
|
601
|
0
|
|
|
|
|
|
$hr_search = { 'me.id' => $i_testrun_id } |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
} |
604
|
|
|
|
|
|
|
elsif ( @a_given_options ) { |
605
|
|
|
|
|
|
|
|
606
|
0
|
0
|
|
|
|
|
if ( my @a_state_selection = grep { $h_options{$_} } qw/ finished running schedule prepare / ) { |
|
0
|
|
|
|
|
|
|
607
|
0
|
|
0
|
|
|
|
$hr_search->{'testrun_scheduling.status'} ||= \@a_state_selection; |
608
|
|
|
|
|
|
|
} |
609
|
|
|
|
|
|
|
|
610
|
0
|
0
|
|
|
|
|
if ( my $ar_queue_ids = ar_get_queue_ids( $or_schema, $h_options{queue} ) ) { |
611
|
0
|
|
|
|
|
|
$hr_search->{'testrun_scheduling.queue_id'} = { -in => $ar_queue_ids }; |
612
|
|
|
|
|
|
|
} |
613
|
|
|
|
|
|
|
|
614
|
0
|
0
|
|
|
|
|
if ( my $ar_host_ids = ar_get_queue_ids( $or_schema, $h_options{host} ) ) { |
615
|
|
|
|
|
|
|
$hr_search->{-or} = [ |
616
|
0
|
|
|
|
|
|
-and => [ |
617
|
|
|
|
|
|
|
-not => { 'testrun_scheduling.status' => 'schedule', }, |
618
|
|
|
|
|
|
|
'testrun_scheduling.host_id' => { -in => $ar_host_ids, }, |
619
|
|
|
|
|
|
|
], |
620
|
|
|
|
|
|
|
-and => [ |
621
|
|
|
|
|
|
|
'testrun_scheduling.status' => 'schedule', |
622
|
|
|
|
|
|
|
'requested_hosts.host_id' => { -in => $ar_host_ids, }, |
623
|
|
|
|
|
|
|
], |
624
|
|
|
|
|
|
|
]; |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
0
|
|
|
|
|
|
my $hr_search_options = { |
630
|
|
|
|
|
|
|
order_by => { -desc => 'me.id' } |
631
|
|
|
|
|
|
|
}; |
632
|
|
|
|
|
|
|
|
633
|
0
|
0
|
|
|
|
|
if ( exists $h_options{limit} ) { |
634
|
0
|
0
|
|
|
|
|
if ( $h_options{limit} > 0 ) { |
635
|
0
|
|
|
|
|
|
$hr_search_options->{rows} = $h_options{limit}; |
636
|
|
|
|
|
|
|
} |
637
|
|
|
|
|
|
|
} |
638
|
|
|
|
|
|
|
else { |
639
|
0
|
|
|
|
|
|
$hr_search_options->{rows} = $i_limit_default; |
640
|
|
|
|
|
|
|
} |
641
|
0
|
0
|
|
|
|
|
if ( $h_options{verbose} ) { |
642
|
0
|
|
|
|
|
|
$hr_search_options->{'prefetch'} = [ |
643
|
|
|
|
|
|
|
{ |
644
|
|
|
|
|
|
|
'testrun_scheduling' => [ |
645
|
|
|
|
|
|
|
'queue', |
646
|
|
|
|
|
|
|
{ |
647
|
|
|
|
|
|
|
'requested_hosts' => 'host', |
648
|
|
|
|
|
|
|
}, |
649
|
|
|
|
|
|
|
], |
650
|
|
|
|
|
|
|
}, |
651
|
|
|
|
|
|
|
{ |
652
|
|
|
|
|
|
|
'testrun_scheduling' => 'host', |
653
|
|
|
|
|
|
|
}, |
654
|
|
|
|
|
|
|
]; |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
else { |
657
|
0
|
|
|
|
|
|
$hr_search_options->{'join'} = 'testrun_scheduling'; |
658
|
0
|
|
|
|
|
|
$hr_search_options->{'select'} = ['id']; |
659
|
|
|
|
|
|
|
} |
660
|
|
|
|
|
|
|
|
661
|
0
|
|
|
|
|
|
my $or_testrun_rs = |
662
|
|
|
|
|
|
|
$or_schema |
663
|
|
|
|
|
|
|
->resultset('Testrun') |
664
|
|
|
|
|
|
|
->search( $hr_search, $hr_search_options ) |
665
|
|
|
|
|
|
|
; |
666
|
|
|
|
|
|
|
|
667
|
0
|
0
|
|
|
|
|
if ( $h_options{verbose} ) { |
668
|
0
|
|
|
|
|
|
Tapper::CLI::Testrun::b_print_testruns( $or_testrun_rs ); |
669
|
|
|
|
|
|
|
} |
670
|
|
|
|
|
|
|
else { |
671
|
0
|
|
|
|
|
|
foreach my $i_testrun_id ( $or_testrun_rs->get_column('id')->all ) { |
672
|
0
|
|
|
|
|
|
say $i_testrun_id; |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
} |
675
|
|
|
|
|
|
|
|
676
|
0
|
0
|
|
|
|
|
if ( $ENV{DEBUG} ) { |
677
|
|
|
|
|
|
|
|
678
|
0
|
|
|
|
|
|
require DBIx::Class::QueryLog::Analyzer; |
679
|
0
|
|
|
|
|
|
my $or_analyzer = DBIx::Class::QueryLog::Analyzer->new({ |
680
|
|
|
|
|
|
|
querylog => $or_querylog, |
681
|
|
|
|
|
|
|
}); |
682
|
|
|
|
|
|
|
|
683
|
0
|
|
|
|
|
|
require Data::Dumper; |
684
|
0
|
|
|
|
|
|
say {*STDERR} "Query count: " . scalar( @{$or_analyzer->get_sorted_queries} ); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
685
|
0
|
|
|
|
|
|
say {*STDERR} Data::Dumper::Dumper([ |
|
0
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
$or_analyzer->get_sorted_queries |
687
|
|
|
|
|
|
|
]); |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
} |
690
|
|
|
|
|
|
|
|
691
|
0
|
|
|
|
|
|
return; |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
sub testrun_update |
697
|
|
|
|
|
|
|
{ |
698
|
0
|
|
|
0
|
1
|
|
my ($c) = @_; |
699
|
0
|
|
|
|
|
|
$c->getopt( 'id=i@','status=s', 'topic=s', 'auto-rerun!','help|?', 'verbose|v' ); |
700
|
0
|
0
|
0
|
|
|
|
if ( $c->options->{help} or not $c->options->{id}) { |
701
|
0
|
0
|
|
|
|
|
say STDERR "Please set at least one testrun id with --id!" unless @{$c->options->{id} || []}; |
|
0
|
0
|
|
|
|
|
|
702
|
0
|
0
|
0
|
|
|
|
say STDERR "Please set an update action" unless ($c->options->{state} or defined $c->options->{"auto-rerun"}); |
703
|
0
|
|
|
|
|
|
say STDERR "$PROGRAM_NAME testrun-update --id=s@ --status=s --auto_rerun --no-auto-rerun --verbose|v [--help|?]"; |
704
|
0
|
|
|
|
|
|
say STDERR " --id Id of the testrun to update, can be given multiple times"; |
705
|
0
|
|
|
|
|
|
say STDERR " --topic one of: Kernel, Xen, KVM, Hardware, Distribution, Benchmark, Software, Misc"; |
706
|
0
|
|
|
|
|
|
say STDERR " --status Set testrun to given status, can be one of 'prepare', 'schedule', 'finished'."; |
707
|
0
|
|
|
|
|
|
say STDERR " --auto-rerun Activate auto-rerun on testrun. "; |
708
|
0
|
|
|
|
|
|
say STDERR " --no-auto-rerun Activate auto-rerun on testrun"; |
709
|
0
|
|
|
|
|
|
say STDERR " --verbose|v Print new state of testrun (will only print id of updated testruns without)"; |
710
|
0
|
|
|
|
|
|
say STDERR " --help|? Print this help message and exit"; |
711
|
0
|
|
|
|
|
|
return; |
712
|
|
|
|
|
|
|
} |
713
|
|
|
|
|
|
|
|
714
|
0
|
|
|
|
|
|
require Tapper::Model; |
715
|
|
|
|
|
|
|
ID: |
716
|
0
|
|
|
|
|
|
foreach my $testrun_id (@{$c->options->{id}}) { |
|
0
|
|
|
|
|
|
|
717
|
0
|
|
|
|
|
|
my $testrun = Tapper::Model::model('TestrunDB')->resultset('Testrun')->find($testrun_id); |
718
|
0
|
0
|
|
|
|
|
if (not $testrun) { |
719
|
0
|
|
|
|
|
|
say STDERR "Testrun with id $testrun_id not found. Skipping!"; |
720
|
0
|
|
|
|
|
|
next ID; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
0
|
0
|
|
|
|
|
if ( $c->options->{topic} ) { |
724
|
|
|
|
|
|
|
$testrun->update_content({ |
725
|
|
|
|
|
|
|
topic => $c->options->{topic}, |
726
|
0
|
|
|
|
|
|
}); |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
|
729
|
0
|
0
|
0
|
|
|
|
if (not ($testrun->testrun_scheduling->status eq 'prepare' or |
730
|
|
|
|
|
|
|
$testrun->testrun_scheduling->status eq 'schedule') |
731
|
|
|
|
|
|
|
) |
732
|
|
|
|
|
|
|
{ |
733
|
0
|
|
|
|
|
|
say STDERR "Can only update testruns in state 'schedule' and 'finished'. Updating testruns in other states will break something. Please consider tapper testrun-rerun"; |
734
|
0
|
|
|
|
|
|
next ID; |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
|
737
|
0
|
0
|
|
|
|
|
if ($c->options->{status}) { |
738
|
0
|
|
|
|
|
|
$testrun->testrun_scheduling->status($c->options->{status}); |
739
|
0
|
|
|
|
|
|
$testrun->testrun_scheduling->update; |
740
|
|
|
|
|
|
|
} |
741
|
0
|
0
|
|
|
|
|
if (defined($c->options->{"auto-rerun"})) { |
742
|
0
|
|
|
|
|
|
$testrun->testrun_scheduling->auto_rerun($c->options->{"auto-rerun"}); |
743
|
0
|
|
|
|
|
|
$testrun->testrun_scheduling->update; |
744
|
|
|
|
|
|
|
} |
745
|
0
|
0
|
|
|
|
|
if ($c->options->{verbose}) { |
746
|
0
|
|
|
|
|
|
b_print_testruns($testrun); |
747
|
|
|
|
|
|
|
} else { |
748
|
0
|
|
|
|
|
|
say $testrun_id; |
749
|
|
|
|
|
|
|
} |
750
|
|
|
|
|
|
|
} |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
} |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
sub b_cancel |
756
|
|
|
|
|
|
|
{ |
757
|
|
|
|
|
|
|
|
758
|
0
|
|
|
0
|
1
|
|
my ($c) = @_; |
759
|
0
|
|
|
|
|
|
$c->getopt( 'id=i@','comment=s','help|?', 'verbose|v' ); |
760
|
0
|
0
|
0
|
|
|
|
if ( $c->options->{help} or not $c->options->{id}) { |
761
|
0
|
0
|
|
|
|
|
say STDERR "Please set at least one testrun id with --id!" unless @{$c->options->{id} || []}; |
|
0
|
0
|
|
|
|
|
|
762
|
0
|
|
|
|
|
|
say STDERR "$PROGRAM_NAME testrun-cancel --id=i@ [--comment=s] [--verbose|v] [--help|?]"; |
763
|
0
|
|
|
|
|
|
say STDERR " --id Id of the testrun to cancel, can be given multiple times"; |
764
|
0
|
|
|
|
|
|
say STDERR " --comment A comment why the testrun(s) were cancelled"; |
765
|
0
|
|
|
|
|
|
say STDERR " --verbose|v Tell user what we just did (without -v only the testrun id will be printed in the success case)"; |
766
|
0
|
|
|
|
|
|
say STDERR " --help|? Print this help message and exit"; |
767
|
0
|
|
|
|
|
|
return; |
768
|
|
|
|
|
|
|
} |
769
|
|
|
|
|
|
|
|
770
|
0
|
|
|
|
|
|
require Tapper::Cmd::Testrun; |
771
|
0
|
|
|
|
|
|
my $cmd = Tapper::Cmd::Testrun->new(); |
772
|
0
|
|
|
|
|
|
foreach my $id (@{$c->options->{id}}) { |
|
0
|
|
|
|
|
|
|
773
|
0
|
|
|
|
|
|
my $retval = $cmd->cancel($id, $c->options->{comment}); |
774
|
0
|
0
|
|
|
|
|
warn $retval if $retval; |
775
|
|
|
|
|
|
|
} |
776
|
0
|
|
|
|
|
|
return; |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
sub b_pause |
782
|
|
|
|
|
|
|
{ |
783
|
|
|
|
|
|
|
|
784
|
0
|
|
|
0
|
1
|
|
my ($c) = @_; |
785
|
0
|
|
|
|
|
|
$c->getopt( 'id=i@','help|?', 'verbose|v' ); |
786
|
0
|
0
|
0
|
|
|
|
if ( $c->options->{help} or not $c->options->{id}) { |
787
|
0
|
0
|
|
|
|
|
say STDERR "Please set at least one testrun id with --id!" unless @{$c->options->{id} || []}; |
|
0
|
0
|
|
|
|
|
|
788
|
0
|
|
|
|
|
|
say STDERR "$PROGRAM_NAME testrun-pause --id=i@ [--verbose|v] [--help|?]"; |
789
|
0
|
|
|
|
|
|
say STDERR " --id Id of the testrun to pause, can be given multiple times"; |
790
|
0
|
|
|
|
|
|
say STDERR " --help|? Print this help message and exit"; |
791
|
0
|
|
|
|
|
|
return; |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
|
794
|
0
|
|
|
|
|
|
require Tapper::Cmd::Testrun; |
795
|
0
|
|
|
|
|
|
my $cmd = Tapper::Cmd::Testrun->new(); |
796
|
0
|
|
|
|
|
|
foreach my $id (@{$c->options->{id}}) { |
|
0
|
|
|
|
|
|
|
797
|
0
|
|
|
|
|
|
my $retval = $cmd->pause($id); |
798
|
0
|
0
|
|
|
|
|
say $id if $retval; |
799
|
|
|
|
|
|
|
} |
800
|
0
|
|
|
|
|
|
return; |
801
|
|
|
|
|
|
|
} |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
sub b_continue |
806
|
|
|
|
|
|
|
{ |
807
|
|
|
|
|
|
|
|
808
|
0
|
|
|
0
|
1
|
|
my ($c) = @_; |
809
|
0
|
|
|
|
|
|
$c->getopt( 'id=i@','help|?', 'verbose|v' ); |
810
|
0
|
0
|
0
|
|
|
|
if ( $c->options->{help} or not $c->options->{id}) { |
811
|
0
|
0
|
|
|
|
|
say STDERR "Please set at least one testrun id with --id!" unless @{$c->options->{id} || []}; |
|
0
|
0
|
|
|
|
|
|
812
|
0
|
|
|
|
|
|
say STDERR "$PROGRAM_NAME testrun-continue --id=i@ [--verbose|v] [--help|?]"; |
813
|
0
|
|
|
|
|
|
say STDERR " --id Id of the testrun to continue, can be given multiple times"; |
814
|
0
|
|
|
|
|
|
say STDERR " --help|? Print this help message and exit"; |
815
|
0
|
|
|
|
|
|
return; |
816
|
|
|
|
|
|
|
} |
817
|
|
|
|
|
|
|
|
818
|
0
|
|
|
|
|
|
require Tapper::Cmd::Testrun; |
819
|
0
|
|
|
|
|
|
my $cmd = Tapper::Cmd::Testrun->new(); |
820
|
0
|
|
|
|
|
|
foreach my $id (@{$c->options->{id}}) { |
|
0
|
|
|
|
|
|
|
821
|
0
|
|
|
|
|
|
my $retval = $cmd->continue($id); |
822
|
0
|
0
|
|
|
|
|
say $id if $retval; |
823
|
|
|
|
|
|
|
} |
824
|
0
|
|
|
|
|
|
return; |
825
|
|
|
|
|
|
|
} |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
sub setup { |
830
|
|
|
|
|
|
|
|
831
|
0
|
|
|
0
|
1
|
|
my ( $or_apprad ) = @_; |
832
|
|
|
|
|
|
|
|
833
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-list' , \&b_list_testrun , 'Show all testruns matching a given condition', ); |
834
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-update' , \&testrun_update , 'Update an existing testrun', ); |
835
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-rerun' , \&b_rerun , 'Rerun an existing testrun with the same preconditions', ); |
836
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-delete' , \&b_delete , 'Delete a testrun', ); |
837
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-pause' , \&b_pause , 'Pause a not-yet-running testrun', ); |
838
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-continue',\&b_continue , 'Continue a paused testrun', ); |
839
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-cancel' , \&b_cancel , 'Cancel a running testrun', ); |
840
|
0
|
|
|
|
|
|
$or_apprad->register( 'testrun-new' , \&b_create_testrun , 'Create a testrun', ); |
841
|
|
|
|
|
|
|
|
842
|
0
|
0
|
|
|
|
|
if ( $or_apprad->can('group_commands') ) { |
843
|
0
|
|
|
|
|
|
$or_apprad->group_commands( |
844
|
|
|
|
|
|
|
'Testrun commands', |
845
|
|
|
|
|
|
|
'testrun-list', |
846
|
|
|
|
|
|
|
'testrun-new', |
847
|
|
|
|
|
|
|
'testrun-update', |
848
|
|
|
|
|
|
|
'testrun-rerun', |
849
|
|
|
|
|
|
|
'testrun-delete', |
850
|
|
|
|
|
|
|
'testrun-pause', |
851
|
|
|
|
|
|
|
'testrun-continue', |
852
|
|
|
|
|
|
|
'testrun-cancel', |
853
|
|
|
|
|
|
|
); |
854
|
|
|
|
|
|
|
} |
855
|
|
|
|
|
|
|
|
856
|
0
|
|
|
|
|
|
return; |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
1; # End of Tapper::CLI |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
__END__ |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
=pod |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
=encoding UTF-8 |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
=head1 NAME |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
Tapper::CLI::Testrun |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
=head1 SYNOPSIS |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
This module is part of the Tapper::CLI framework. It is supposed to be |
875
|
|
|
|
|
|
|
used together with App::Rad. All following functions expect their |
876
|
|
|
|
|
|
|
arguments as $c->options->{$arg} unless otherwise stated. |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
use App::Rad; |
879
|
|
|
|
|
|
|
use Tapper::CLI::Testrun; |
880
|
|
|
|
|
|
|
Tapper::CLI::Testrun::setup($c); |
881
|
|
|
|
|
|
|
App::Rad->run(); |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
=head1 NAME |
884
|
|
|
|
|
|
|
|
885
|
|
|
|
|
|
|
Tapper::CLI::Testrun - Tapper - testrun related commands for the tapper CLI |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
=head1 FUNCTIONS |
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
=head2 b_print_single_testrun |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
print column data for a single testrun row to STDOUT |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
=head2 b_print_testruns |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
print column data for resultset to STDOUT |
896
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
=head2 ar_get_list_testrun_parameters |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
return list testrun parameters and descriptions |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
=head2 ar_get_rerun_testrun_parameters |
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
return rerun testrun parameters and descriptions |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
=head2 ar_get_delete_testrun_parameters |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
return delete testrun parameters and descriptions |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
=head2 ar_get_create_testrun_parameters |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
return create testrun parameters and descriptions |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
=head2 create_macro_preconditions |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
Process a macroprecondition. This includes substitions using |
916
|
|
|
|
|
|
|
Template::Toolkit, separating the individual preconditions that are part of |
917
|
|
|
|
|
|
|
the macroprecondition and putting them into the database. Parameters fit the |
918
|
|
|
|
|
|
|
App::Cmd::Command API. |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
@param hashref - hash containing options |
921
|
|
|
|
|
|
|
@param hashref - hash containing arguments |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
@returnlist array containing precondition ids |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
=head2 s_create_testrun_parameter_check |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
check command line parameters for create testrun and return error if exists |
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
=head2 b_create_testrun |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
create a testrun |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
=head2 b_delete |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
delete a testrun |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
=head2 b_rerun |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
rerun an existing testrun |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
=head2 ar_get_queue_ids |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
return an array reference of queue_ids for a an array reference of queue_names |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
=head2 ar_get_host_ids |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
return an array reference of host_ids for a an array reference of host_names |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
=head2 b_list_testrun |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
list existing restuns |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
=head2 testrun_update |
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
Update values of an existing testrun. |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=head2 b_cancel |
958
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
Cancel a running testrun. If the given testrun is currently not running, |
960
|
|
|
|
|
|
|
the function does the obvious right thing and also warns the user. |
961
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=head2 b_pause |
963
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
Pause a not-yet-running testrun. |
965
|
|
|
|
|
|
|
|
966
|
|
|
|
|
|
|
=head2 b_continue |
967
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
Continue a paused testrun. |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
=head2 setup |
971
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
Initialize the testplan functions for tapper CLI |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
=head1 AUTHOR |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
AMD OSRC Tapper Team <tapper@amd64.org> |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
This software is Copyright (c) 2020 by Advanced Micro Devices, Inc.. |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
This is free software, licensed under: |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
The (two-clause) FreeBSD License |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
=cut |