4 # Copyright (c) 2018 Vojtech Horky
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
11 # - Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # - Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 # - The name of the author may not be used to endorse or promote products
17 # derived from this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 from htest
.utils
import retries
38 Base class for individual tasks that are executed in a scenario.
41 def __init__(self
, name
):
43 Set @name to the task name (call from subclass).
48 self
.fail_message
= ''
49 self
.logger
= logging
.getLogger(name
)
51 def check_required_argument(self
, args
, name
):
53 To be used by subclasses to check that arguments
57 raise Exception("Required argument {} missing.".format(name
))
59 def is_vm_launcher(self
):
61 Whether this task starts a new VM.
68 def set_machine(self
, machine
):
70 Set machine responsible for executing this task.
72 self
.machine
= machine
76 Actually execute this task.
80 class ScenarioTaskBoot(ScenarioTask
):
82 Brings the machine up.
84 def __init__(self
, args
):
85 ScenarioTask
.__init
__(self
, 'boot')
88 def is_vm_launcher(self
):
94 class ScenarioTaskCommand(ScenarioTask
):
96 Run a command in vterm.
99 def __init__(self
, args
):
100 ScenarioTask
.__init
__(self
, 'command')
101 if type(args
) is str:
102 args
= { 'args': args
}
103 self
.check_required_argument(args
, 'args')
104 self
.command
= args
['args']
105 self
.ignore_abort
= False
106 if 'ignoreabort' in args
:
107 self
.ignore_abort
= args
['ignoreabort']
110 def _find_in_lines(self
, text
, lines
):
112 if l
.find(text
) != -1:
116 def _grep_in_lines(self
, comp_re
, lines
):
118 if comp_re
.search(l
) is not None:
123 self
.logger
.info("Typing '{}' into {}.".format(self
.command
, self
.machine
.name
))
125 cursor_symbol
= self
.machine
.get_vterm_cursor_symbol()
126 cursor_symbol_spaced
= '' if cursor_symbol
== '' else ' ' + cursor_symbol
127 prompt_re
= re
.compile('^/[^ ]* #' + re
.escape(cursor_symbol_spaced
) + '[\t ]*$')
128 self
.logger
.debug("RE for prompt matching: {}".format(prompt_re
))
130 # Capture the screen before typing the command.
131 self
.machine
.capture_vterm()
133 self
.machine
.type(self
.command
)
135 # Wait until the command is fully displayed on the screen.
136 # That is needed to properly detect the newly displayed lines.
137 # FIXME: this will not work for long commands spanning multiple lines
138 for xxx
in retries(timeout
=60, interval
=2, name
="vterm-type", message
="Failed to type command"):
139 self
.machine
.vterm
= []
140 self
.machine
.capture_vterm()
141 lines
= self
.machine
.vterm
144 line
= lines
[0].strip()
146 if (cursor_symbol
!= '') and line
.endswith(cursor_symbol
):
147 line
= line
[0:-(len(cursor_symbol
))]
148 if line
.endswith(self
.command
.strip()):
151 self
.machine
.vterm
= []
152 self
.machine
.type('\n')
154 # Read output of the command.
155 # We wait until prompt reappears or we find some text that is not
156 # supposed to be there. Meanwhile we check that the text that is
157 # supposed to be there appears.
158 asserted_text_found
= not 'assert' in self
.args
159 for xxx
in retries(timeout
=60, interval
=2, name
="vterm-run", message
="Failed to run command"):
160 self
.logger
.debug("self.vterm = {}".format(self
.machine
.vterm
))
161 self
.machine
.capture_vterm()
162 lines
= self
.machine
.vterm
163 self
.logger
.debug("Read lines {}".format(lines
))
164 self
.machine
.vterm
= []
165 if not self
.ignore_abort
:
166 if self
._find
_in
_lines
('Cannot spawn', lines
) or self
._find
_in
_lines
('Command failed', lines
):
167 raise Exception('Failed to run command')
168 if 'negassert' in self
.args
:
169 if self
._find
_in
_lines
(self
.args
['negassert'], lines
):
170 raise Exception('Found forbidden text {} ...'.format(self
.args
['negassert']))
171 if ('assert' in self
.args
) and (not asserted_text_found
):
172 asserted_text_found
= self
._find
_in
_lines
(self
.args
['assert'], lines
)
173 if self
._grep
_in
_lines
(prompt_re
, lines
):
174 if not asserted_text_found
:
175 raise Exception('Missing expected text {} ...'.format(self
.args
['assert']))
177 self
.logger
.info("Command '{}' done.".format(self
.command
))
179 class ScenarioTaskCls(ScenarioTask
):
184 def __init__(self
, args
):
185 ScenarioTask
.__init
__(self
, 'vterm-cls')
188 self
.logger
.info("Clearing the screen.")
191 self
.machine
.type('\n')