File Coverage

OpenQA/Isotovideo/CommandHandler.pm
Criterion Covered Total %
statement 235 253 92.8
total 235 253 92.8


line stmt code
1   # Copyright 2018-2019 SUSE LLC
2   # SPDX-License-Identifier: GPL-2.0-or-later
3    
4   use Mojo::Base 'Mojo::EventEmitter', -signatures;
5 30  
  30  
  30  
6   use bmwqemu;
7 30 use testapi 'diag';
  30  
  30  
8 30 use OpenQA::Isotovideo::Interface;
  30  
  30  
9 30 use Cpanel::JSON::XS;
  30  
  30  
10 30 use Mojo::File 'path';
  30  
  30  
11 30  
  30  
  30  
12   use constant AUTOINST_STATUSFILE => 'autoinst-status.json';
13 30  
  30  
  30  
14    
15   # io handles for sending data to command server and backend
16   has [qw(cmd_srv_fd backend_fd answer_fd)] => undef;
17    
18   # the name of the current test (full name includes category prefix, eg. installation-)
19   has [qw(current_test_name current_test_full_name)];
20    
21   # the currently processed test API function
22   has current_api_function => undef;
23    
24   # status = ( initial | running | finished )
25   # set to running when first test starts
26   has status => 'initial';
27    
28   # conditions when to pause
29   has pause_test_name => sub { $bmwqemu::vars{PAUSE_AT} };
30   # (set to name of a certain test module, with or without category)
31   has pause_on_screen_mismatch => sub { $bmwqemu::vars{PAUSE_ON_SCREEN_MISMATCH} };
32   # (set to 'assert_screen' or 'check_screen' where 'check_screen' includes 'assert_screen')
33   has pause_on_next_command => sub { $bmwqemu::vars{PAUSE_ON_NEXT_COMMAND} // 0 };
34   # (set to 0 or 1)
35    
36   # the reason why the test execution has paused or 0 if not paused
37   has reason_for_pause => 0;
38    
39   # when paused, save the command from autotest which has been postponed to be able to resume
40   has postponed_answer_fd => undef;
41   has postponed_command => undef;
42    
43   # properties consumed by isotovideo::check_asserted_screen
44   # * timeout for the select (only set for check_screens)
45   # * tags received from 'set_tags_to_assert' command
46   # * do not wait for timeout if set
47   has [qw(timeout no_wait tags)];
48    
49   # set to the socket we have to send replies to when the backend is done
50   has backend_requester => undef;
51    
52   # whether the test has already been completed and whether it has died
53   has [qw(test_completed test_died)] => 0;
54    
55   $self->tags(undef);
56 9 $self->timeout(undef);
  9  
  9  
57 9 }
58 9  
59   # processes the $response and send the answer back via $answer_fd by invoking one of the subsequent handler methods
60   # note: To add a new command, create a handler method called "_handle_command_<new_command_name>".
61   my $cmd = $command_to_process->{cmd} or die 'isotovideo: no command specified';
62   $self->answer_fd($answer_fd);
63 184  
  184  
  184  
  184  
  184  
64 184 # invoke handler for the command
65 184 if (my $handler = $self->can('_handle_command_' . $cmd)) {
66   return $handler->($self, $command_to_process, $cmd);
67   }
68 184 if ($cmd =~ m/^backend_(.*)/) {
69 91 return $self->_pass_command_to_backend_unless_paused($command_to_process, $1);
70   }
71 93  
72 93 die 'isotovideo: unknown command ' . $cmd;
73   }
74    
75 0  
76   my $cmd = $response->{cmd};
77   my $reason_for_pause = $self->reason_for_pause;
78 3  
  3  
  3  
  3  
79   # check whether we're supposed to pause on the next command if there's no other reason to pause anyways
80 106 if (!$reason_for_pause && $self->pause_on_next_command) {
  106  
  106  
  106  
81 106 $self->reason_for_pause($reason_for_pause = "reached $cmd and pause on next command enabled");
82 106 }
83    
84   return unless $reason_for_pause;
85 106  
86 1 # emit info
87   $self->_send_to_cmd_srv({paused => $response, reason => $reason_for_pause});
88   $self->update_status_file;
89 106 diag("isotovideo: paused, so not passing $cmd to backend");
90    
91   # postpone execution of command
92 1 $self->postponed_answer_fd($self->answer_fd);
93 1 $self->postponed_command($response);
94 1  
95   # send no reply to autotest, just let it wait
96   return 1;
97 1 }
98 1  
99    
100    
101 1 myjsonrpc::send_json($self->backend_requester, $data);
102   $self->backend_requester(undef);
103   }
104 152  
  152  
  152  
  152  
  152  
105    
106 93  
  93  
  93  
  93  
  93  
107   return $self->_respond_ok unless my $reason_for_pause = $self->reason_for_pause;
108 93 $self->_send_to_cmd_srv({paused => 1, reason => $reason_for_pause});
  93  
  93  
  93  
109 93 $self->postponed_answer_fd($self->answer_fd)->postponed_command(undef);
110 93 }
111    
112   return if $self->_postpone_backend_command_until_resumed($response);
113 69  
  69  
  69  
  69  
  69  
114   die 'isotovideo: we need to implement a backend queue' if $self->backend_requester;
115 37 $self->backend_requester($self->answer_fd);
  37  
  37  
  37  
116    
117 30 $self->_send_to_cmd_srv({
  30  
  30  
118 30 $backend_cmd => $response,
119 1 current_api_function => $backend_cmd,
120 1 });
121   $self->_send_to_backend({cmd => $backend_cmd, arguments => $response});
122   $self->current_api_function($backend_cmd);
123 93 }
  93  
  93  
  93  
  93  
124 93  
125   return 0 unless my $pause_on_screen_mismatch = $self->pause_on_screen_mismatch;
126 93  
127 93 return 1 if ($pause_on_screen_mismatch eq 'check_screen');
128   return $response->{check} ? 0 : 1 if ($pause_on_screen_mismatch eq 'assert_screen');
129 93 return 0;
130   }
131    
132   if (!$self->_is_configured_to_pause_on_timeout($response)) {
133 93 $self->_respond({ret => 0});
134 93 return;
135   }
136    
137 16 my $reason_for_pause = $response->{msg};
  16  
  16  
  16  
138 16 $self->reason_for_pause($reason_for_pause);
139   $self->_send_to_cmd_srv({paused => $response, reason => $reason_for_pause});
140 7 $self->update_status_file;
141 4 diag('isotovideo: pausing test execution on timeout as requested at ' . $self->current_test_full_name);
142 0  
143   # postpone sending the reply
144   $self->postponed_answer_fd($self->answer_fd);
145 7 $self->postponed_command(undef);
  7  
  7  
  7  
146 7 }
147 5  
148 5 $self->_respond({
149   ret => ($self->_is_configured_to_pause_on_timeout($response) ? 1 : 0)
150   });
151 2 }
152 2  
153 2 my $pause_test_name = $response->{name};
154 2 if ($pause_test_name) {
155 2 diag('isotovideo: test execution will be paused at test ' . $pause_test_name);
156   }
157   elsif ($self->pause_test_name) {
158 2 diag('isotovideo: test execution will no longer be paused at a certain test');
159 2 }
160   $self->pause_test_name($pause_test_name);
161   $self->_send_to_cmd_srv({set_pause_at_test => $pause_test_name});
162 9 $self->_respond_ok();
  9  
  9  
  9  
163 9 }
164    
165   my $pause_on_screen_mismatch = $response->{pause_on};
166   $self->pause_on_screen_mismatch($pause_on_screen_mismatch);
167   $self->_send_to_cmd_srv({set_pause_on_screen_mismatch => ($pause_on_screen_mismatch // Mojo::JSON->false)});
168 2 $self->_respond_ok();
  2  
  2  
  2  
169 2 }
170 2  
171 1 my $set_pause_on_next_command = ($response->{flag} ? 1 : 0);
172   $self->pause_on_next_command($set_pause_on_next_command);
173   $self->_send_to_cmd_srv({set_pause_on_next_command => $set_pause_on_next_command});
174 1 $self->_respond_ok();
175   }
176 2  
177 2 my $postponed_command = $self->postponed_command;
178 2 my $postponed_answer_fd = $self->postponed_answer_fd;
179    
180   diag($self->reason_for_pause ?
181 3 'isotovideo: test execution will be resumed'
  3  
  3  
  3  
182 3 : 'isotovideo: resuming test execution requested but not paused anyways'
183 3 );
184 3 $self->_send_to_cmd_srv({resume_test_execution => $postponed_command});
185 3  
186   # unset paused state to continue passing commands to backend
187   $self->reason_for_pause(0);
188 2  
  2  
  2  
  2  
189 2 $self->update_status_file;
190 2 my $downloader = OpenQA::Isotovideo::NeedleDownloader->new();
191 2 $downloader->download_missing_needles($response->{new_needles} // []);
192 2  
193   # skip resuming last command if receiving a resume command without having previously postponed an answer
194   # note: This should normally not be the case. However, the JavaScript client can technically send the command
195 2 # to resume at any time and that apparently also happens sometimes in the fullstack test (see poo#101734).
  2  
  2  
  2  
196 2 return undef unless defined $postponed_answer_fd;
197 2  
198   # if no command has been postponed (because paused due to timeout or on set_current_test) just return 1
199 2 if (!$postponed_command) {
200   myjsonrpc::send_json($postponed_answer_fd, {
201   ret => 1,
202   new_needles => $response->{new_needles},
203 2 });
204   $self->postponed_answer_fd(undef);
205   return;
206 2 }
207    
208 2 # resume with postponed command so autotest can continue
209 2 my $cmd = $postponed_command->{cmd};
210 2 diag("isotovideo: resuming, continue passing $cmd to backend");
211    
212   $self->postponed_command(undef);
213   $self->postponed_answer_fd(undef);
214   $self->process_command($postponed_answer_fd, $postponed_command);
215 2 }
216    
217   # Note: It is unclear why we call set_serial_offset here
218 1 $bmwqemu::backend->_send_json({cmd => 'clear_serial_buffer'});
219    
220   my ($test_name, $full_test_name) = ($response->{name}, $response->{full_name});
221   my $pause_test_name = $self->pause_test_name;
222 0 $self->current_test_name($test_name);
223 0 $self->status('running');
224 0 $self->current_test_full_name($full_test_name);
225   $self->_send_to_cmd_srv({
226   set_current_test => $test_name,
227   current_test_full_name => $full_test_name,
228 1 });
229 1  
230   if ($pause_test_name
231 1 && $test_name
232 1 && $full_test_name
233 1 && ($pause_test_name eq $test_name || $pause_test_name eq $full_test_name))
234   {
235   diag("isotovideo: pausing test execution of $pause_test_name because we're supposed to pause at this test module");
236 30 $self->reason_for_pause('reached module ' . $pause_test_name);
  30  
  30  
  30  
237   }
238 30 $self->update_status_file;
239   $self->_respond_ok_or_postpone_if_paused;
240 30 }
241 30  
242 30 $self->test_died($response->{died});
243 30 $self->test_completed($response->{completed});
244 30 $self->emit(tests_done => $response);
245 30 $self->current_test_name('');
246   $self->status('finished');
247   $self->update_status_file;
248   }
249    
250 30 $self->no_wait($response->{no_wait} // 0);
251   return if $self->_postpone_backend_command_until_resumed($response);
252    
253   my %arguments = (
254   mustmatch => $response->{mustmatch},
255 1 timeout => $response->{timeout},
256 1 check => $response->{check},
257   );
258 30 my $current_api_function = $response->{check} ? 'check_screen' : 'assert_screen';
259 30 $self->_send_to_cmd_srv({
260   check_screen => \%arguments,
261   current_api_function => $current_api_function,
262 3 });
  3  
  3  
  3  
263 3 $self->tags($bmwqemu::backend->_send_json(
264 3 {
265 3 cmd => 'set_tags_to_assert',
266 3 arguments => \%arguments,
267 3 })->{tags});
268 3 $self->current_api_function($current_api_function);
269   }
270    
271 13 my $timeout = $response->{timeout};
  13  
  13  
  13  
272 13 $self->_send_to_cmd_srv({set_assert_screen_timeout => $timeout});
273 13 $bmwqemu::backend->_send_json({
274   cmd => 'set_assert_screen_timeout',
275   arguments => $timeout,
276   });
277   $self->_respond_ok();
278   }
279 12  
280 12 $self->_respond({
281 12 tags => $self->tags,
282   running => $self->current_test_name,
283   current_test_full_name => $self->current_test_full_name,
284   current_api_function => $self->current_api_function,
285   pause_test_name => $self->pause_test_name,
286   pause_on_screen_mismatch => ($self->pause_on_screen_mismatch // Mojo::JSON->false),
287   pause_on_next_command => $self->pause_on_next_command,
288   test_execution_paused => $self->reason_for_pause,
289 12 devel_mode_major_version => $OpenQA::Isotovideo::Interface::developer_mode_major_version,
290 12 devel_mode_minor_version => $OpenQA::Isotovideo::Interface::developer_mode_minor_version,
291   });
292   }
293 1  
  1  
  1  
  1  
294 1 $self->_respond({
295 1 test_git_hash => $bmwqemu::vars{TEST_GIT_HASH},
296 1 needles_git_hash => $bmwqemu::vars{NEEDLES_GIT_HASH},
297   version => $OpenQA::Isotovideo::Interface::version,
298   });
299   }
300 1  
301   # This will stop to work if we change the serialfile after the initialization because of the fork
302   my ($serial, $pos) = $bmwqemu::backend->{backend}->read_serial($response->{position});
303 1 $self->_respond({serial => $serial, position => $pos});
  1  
  1  
  1  
304 1 }
305    
306   delete $response->{cmd};
307   delete $response->{json_cmd_token};
308   $self->_send_to_cmd_srv($response);
309   $self->_respond_ok();
310   }
311    
312   my $coder = Cpanel::JSON::XS->new->pretty->canonical;
313   my $data = {
314   test_execution_paused => $self->reason_for_pause,
315   status => $self->status,
316   current_test => $self->current_test_name,
317   };
318 0 my $json = $coder->encode($data);
  0  
  0  
  0  
319    
320   my $tmp = AUTOINST_STATUSFILE . ".$$.tmp";
321   path($tmp)->spurt($json);
322 0 rename $tmp, AUTOINST_STATUSFILE or die $!;
323   }
324    
325   1;