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.
37 from htest
.utils
import retries
, format_command
, format_command_pipe
38 from htest
.vm
.controller
import VMController
40 class QemuVMController(VMController
):
52 'arm32/integratorcp': [
74 def __init__(self
, arch
, name
, boot_image
):
75 VMController
.__init
__(self
, 'QEMU-' + arch
)
79 self
.boot_image
= boot_image
80 self
.logger
= logging
.getLogger('qemu-{}'.format(name
))
82 def is_supported(arch
):
83 return arch
in QemuVMController
.config
85 def _get_image_dimensions(self
, filename
):
86 im
= Image
.open(filename
)
87 width
, height
= im
.size
89 return ( width
, height
)
91 def _check_is_up(self
):
93 raise Exception("Machine not launched")
95 def _send_command(self
, command
):
97 self
.logger
.debug("Sending command '{}'".format(command
))
98 command
= command
+ '\n'
99 self
.monitor
.sendall(command
.encode('utf-8'))
101 def _run_command(self
, command
):
102 proc
= subprocess
.Popen(command
)
104 if proc
.returncode
!= 0:
105 raise Exception("Command {} failed.".format(command
))
107 def _run_pipe(self
, commands
):
108 self
.logger
.debug("Running pipe {}".format(format_command_pipe(commands
)))
110 for command
in commands
:
113 inp
= procs
[-1].stdout
114 proc
= subprocess
.Popen(command
, stdout
=subprocess
.PIPE
, stdin
=inp
)
116 procs
[-1].communicate()
119 def boot(self
, **kwargs
):
120 self
.monitor_file
= self
.get_temp('monitor')
122 for opt
in QemuVMController
.config
[self
.arch
]:
124 opt
= self
.boot_image
125 elif opt
== '{MEMORY}':
126 opt
= '{}'.format(self
.memory
)
128 cmd
.append('-monitor')
129 cmd
.append('unix:{},server,nowait'.format(self
.monitor_file
))
130 self
.logger
.debug("Starting QEMU: {}".format(format_command(cmd
)))
132 self
.proc
= subprocess
.Popen(cmd
)
133 self
.monitor
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
134 for xxx
in retries(timeout
=30, interval
=2, name
="ctl-socket", message
="Failed to connect to QEMU control socket."):
136 self
.monitor
.connect(self
.monitor_file
)
138 except FileNotFoundError
:
140 except ConnectionRefusedError
:
143 raise Exception("QEMU not started, aborting.")
146 self
.logger
.info("Machine started.")
151 uspace_booted
= False
152 for xxx
in retries(timeout
=3*60, interval
=5, name
="vterm", message
="Failed to boot into userspace"):
156 if l
.find('to see a few survival tips') != -1:
163 self
.full_vterm
= self
.vterm
165 self
.logger
.info("Machine booted into userspace.")
169 def capture_vterm_impl(self
):
170 screenshot_full
= self
.get_temp('screen-full.ppm')
171 screenshot_term
= self
.get_temp('screen-term.png')
172 screenshot_text
= self
.get_temp('screen-term.txt')
174 self
._send
_command
('screendump ' + screenshot_full
)
176 for xxx
in retries(timeout
=5, interval
=1, name
="scrdump", message
="Failed to capture screen"):
181 '-crop', '640x480+4+24',
191 width
, height
= self
._get
_image
_dimensions
(screenshot_term
)
198 '-crop', '{}x{}'.format(cols
* 8, rows
* 16),
207 '-e', 's|[0-9]*,[0-9]*: ([^)]*)[ ]*#\\([0-9A-Fa-f]\\{6\\}\\).*|\\1|',
209 '-e', 's#000000#0#g',
212 [ 'tee', self
.get_temp('1.txt') ],
216 '-e', 'N;s#\\n##;s#^@##;/@$/{s#@$##p;d}',
219 [ 'tee', self
.get_temp('2.txt') ],
228 [ 'tee', self
.get_temp('3.txt') ],
235 '-w', '{}'.format(cols
),
237 [ 'tee', self
.get_temp('4.txt') ],
240 '-n', '{}'.format(rows
),
248 self
.screenshot_filename
= screenshot_full
250 with
open(screenshot_text
, 'r') as f
:
251 lines
= [ l
.strip('\n') for l
in f
.readlines() ]
252 self
.logger
.debug("Captured text:")
254 self
.logger
.debug("| " + l
)
260 self
._send
_command
('quit')
261 VMController
.terminate(self
)
263 def type(self
, what
):
273 if letter
in translations
:
274 letter
= translations
[letter
]
275 self
._send
_command
('sendkey ' + letter
)