4 # Copyright (c) 2016 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.
32 # This code was originally inspired by "virtual testing lab" scripts created
33 # by Jan Buchar for testing his firewall implementation in HelenOS:
34 # https://code.launchpad.net/~teyras/hpf-virtlab/trunk
46 $XX_DEBUG_ECHO " :" "$@"
55 $XX_ACTIVITY_ECHO " +" "$@"
63 ## Prepares pipe to QEMU monitor.
64 # Feed QEMU commands to stdin of this function.
66 # Do not forget to check for return code as checking it inside pipe
67 # is not very useful (we cannot terminate the script from that).
69 # @param 1 Machine id.
70 xx_do_qemu_command_pipe
() {
71 socat STDIN
"UNIX-CONNECT:$XX_TEMP/$1.monitor"
74 ## Sends command to QEMU monitor.
75 # This function terminates the scenario on failure of the monitoring pipe.
77 # @param 1 Machine id.
78 # @param 2 Command to send.
79 xx_do_qemu_command
() {
80 xx_debug
"echo '$2' | socat STDIN 'UNIX-CONNECT:$XX_TEMP/$1.monitor'"
81 echo "$2" | socat STDIN
"UNIX-CONNECT:$XX_TEMP/$1.monitor"
83 if [ $res -ne 0 ]; then
88 ## Types the text to a specific machine.
90 # @param 1 Machine id.
91 # @param 2 Text to type.
93 ( echo "$2" |
fold -w 1 |
sed \
97 -e 's/+/shift-equal/' \
98 -e 's/_/shift-minus/' \
100 -e 's/:/shift-semicolon/' \
103 -e 's/|/shift-backslash/' \
104 -e 's/\([[:upper:]]\)/shift-\L\1/' \
105 -e 's/\\/backslash/' | \
107 xx_do_qemu_command
"$1" "sendkey $code"
111 if [ $res -ne 0 ]; then
124 echo "$i" | cut
'-d=' -f 2-
137 echo "$i" |
grep -v '^[a-zA-Z][a-zA-Z0-9_]*='
142 xx_get_boolean_var
() {
143 value
=`xx_get_var "$@" | tr 'A-Z' 'a-z'`
144 if [ "$value" = "yes" -o "$value" = "true" ]; then
146 elif [ "$value" = "no" -o "$value" = "false" ]; then
156 for i
in $XX_KNOWN_MACHINES; do
157 xx_stop_machine name
="$i"
164 xx_echo
"Option $1 not specified or invalid, terminating."
171 xx_fatal
"Option $1 not specified with no suitable default."
174 if ! echo "$2" |
grep -q "$3"; then
175 xx_fatal
"Option $1 does not match '$3' (got '$2')."
180 xx_do_compute_timeout
() {
181 echo $
(( `date +%s` + 10 * $1 ))
186 local cdrom
=`xx_get_var cdrom "$XX_CDROM_FILE" "$@"`
187 local name
=`xx_get_var name default "$@"`
188 local wait_for_vterm
=`xx_get_boolean_var vterm true "$@"`
190 xx_do_check_var
"xx_start_machine/name" "$name" '^[a-zA-Z][0-9a-zA-Z]*$'
191 xx_do_check_var
"xx_start_machine/cdrom" "$cdrom"
194 if $XX_HEADLESS; then
195 extra_opts
="-display none"
198 extra_opts
="$extra_opts -enable-kvm"
201 xx_echo
"Starting machine $name from $cdrom."
203 local qemu_command
=""
204 if [ $XX_ARCH == "ia32" ]; then
205 qemu_command
=qemu-system-i386
206 elif [ $XX_ARCH == "amd64" ]; then
207 qemu_command
=qemu-system-x86_64
209 if [ -z "$qemu_command" ]; then
210 xx_fatal
"Unable to find proper emulator."
213 xx_run_debug
$qemu_command \
215 -device e1000
,vlan
=0 -net user \
216 -redir udp
:8080::8080 -redir udp
:8081::8081 \
217 -redir tcp
:8080::8080 -redir tcp
:8081::8081 \
218 -redir tcp
:2223::2223 \
221 -daemonize -pidfile "$XX_TEMP/$name.pid" \
222 -monitor "unix:$XX_TEMP/$name.monitor,server,nowait" \
226 xx_do_qemu_command
"$name" "sendkey ret"
228 XX_LAST_MACHINE
=$name
230 XX_KNOWN_MACHINES
="$XX_KNOWN_MACHINES $name"
232 if $wait_for_vterm; then
233 xx_echo2
"Waiting for OS to boot into GUI..."
234 if ! xx_do_wait_for_text
"$name" `xx_do_compute_timeout 15` "to see a few survival tips"; then
235 xx_fatal
"OS have not booted into a known state."
244 local name
=`xx_get_var name $XX_LAST_MACHINE "$@"`
246 xx_echo
"Forcefully killing machine $name."
247 xx_do_qemu_command
"$name" "quit"
250 if [ "$name" = "$XX_LAST_MACHINE" ]; then
256 ## Wait for text to appear on machine console.
258 # @param 1 Machine id.
259 # @param 2 UNIX end-time (date %s + TIME_OUT).
260 # @param 3 Text to match.
261 xx_do_wait_for_text
() {
263 xx_activity
"Taking screenshot, looking for '$3'."
265 xx_do_screenshot
"$1" "$XX_TEMP/$1-full.ppm" \
266 "$XX_TEMP/$1-term.png" "$XX_TEMP/$1-term.txt"
268 if grep -q "$3" <"$XX_TEMP/$1-term.txt"; then
272 if [ `date +%s` -gt $2 ]; then
281 local timeout
=`xx_get_var timeout $XX_DEFAULT_TIMEOUT "$@"`
282 local machine
=`xx_get_var machine $XX_LAST_MACHINE "$@"`
283 local error_msg
=`xx_get_var error "" "$@"`
284 local text
=`xx_get_def_var "$@"`
286 xx_echo
"Checking that '$text' will appear on $machine within ${timeout}s."
288 if ! xx_do_wait_for_text
"$machine" `xx_do_compute_timeout $timeout` "$text"; then
289 xx_fatal
"Failed to recognize '$text' on $machine."
294 local timeout
=`xx_get_var timeout $XX_DEFAULT_TIMEOUT "$@"`
295 local machine
=`xx_get_var machine $XX_LAST_MACHINE "$@"`
296 local error_msg
=`xx_get_var message "" "$@"`
297 local text
=`xx_get_def_var "$@"`
299 xx_echo
"Checking that '$text' will not appear on $machine within ${timeout}s."
301 if xx_do_wait_for_text
"$machine" `xx_do_compute_timeout $timeout` "$text"; then
302 xx_fatal
"Prohibited text '$text' spotted on $machine."
307 local amount
=`xx_get_def_var "$@"`
309 xx_echo
"Waiting for ${amount}s."
315 local machine
=`xx_get_var machine $XX_LAST_MACHINE "$@"`
317 xx_echo
"Clearing the screen on $machine."
318 for i
in `seq 1 35`; do
319 xx_do_qemu_command
"$machine" "sendkey ret"
324 xx_do_ocr_prepare
() {
325 convert
"$1" -crop "8x16" +repage
+adjoin txt
:- \
326 |
sed -e 's|[0-9]*,[0-9]*: ([^)]*)[ ]*#\([0-9A-Fa-f]\{6\}\).*|\1|' -e 's:^#.*:@:' -e 's#000000#0#g' -e 's#FFFFFF#F#' \
327 |
sed -e ':a' -e 'N;s#\n##;s#^@##;/@$/{s#@$##p;d}' -e 't a'
332 xx_do_ocr_prepare
"$1" |
sed -f "$XX_MY_HOME/ocr.sed"
336 xx_do_qemu_command
"$1" "screendump $2"
338 # Looping as the screenshot might take some time to save
341 if convert
"$2" -crop 640x480
+4+24 +repage
-colors 2 -monochrome "$3"; then
344 retries
=$
(( $retries - 1 ))
345 if [ $retries -le 0 ]; then
346 xx_fatal
"Failed to convert screen to monochrome."
352 xx_do_ocr
"$3" |
paste -sd '' |
fold -w 80 >"$4"
357 ## Wait for text to appear on machine console.
359 # @param 1 Machine id.
360 # @param 2 UNIX end-time (date %s + TIME_OUT).
361 # @param 3 Text that shall not be matched.
362 # @param 4 Text that shall be matched before timeout.
363 # @param 5 Consider prompt as a success.
364 # @param 6 Check for standard Bdsh error messages.
365 # @retval 0 Expected text was matched.
366 # @retval 1 Prompt text was matched.
367 # @retval 2 Time-out, nothing matched.
368 # @retval 3 Unexpected text was matched.
369 # @retval 4 Standard Bdsh error message detected.
370 xx_do_complex_text_waiting
() {
371 xx_debug
"waiting: $1/$2 match='$4', no-match='$3'"
372 xx_debug
"waiting: $1/$2 prompt_is_success=$5 check_for_bdsh_error=$6"
375 xx_activity
"Taking screenshot, checking for specific output."
377 xx_do_screenshot
"$1" "$XX_TEMP/$1-full.ppm" \
378 "$XX_TEMP/$1-term.png" "$XX_TEMP/$1-term.txt"
380 if [ -n "$3" ] && grep -q "$3" <"$XX_TEMP/$1-term.txt"; then
384 if [ -n "$4" ] && grep -q "$4" <"$XX_TEMP/$1-term.txt" ; then
388 if $6 && grep -q -e 'Cannot spawn' -e 'Command failed' <"$XX_TEMP/$1-term.txt"; then
392 if $5 && grep -q '^.*/ # _[ ]*$' <"$XX_TEMP/$1-term.txt" ; then
396 if [ `date +%s` -gt $2 ]; then
405 local cmd
=`xx_get_def_var "$@"`
406 local timeout
=`xx_get_var timeout $XX_DEFAULT_TIMEOUT "$@"`
407 local machine
=`xx_get_var machine $XX_LAST_MACHINE "$@"`
408 local error_msg
=`xx_get_var error "" "$@"`
409 local text
=`xx_get_var assert "" "$@"`
410 local negtext
=`xx_get_var die_on "" "$@"`
411 local text_is_empty
=`if [ -n "$text" ]; then echo false; else echo true; fi`
413 xx_echo
"Sending '$cmd' to $machine."
414 xx_do_type
"$machine" "$cmd"
415 xx_do_qemu_command
"$machine" "sendkey ret"
418 xx_do_complex_text_waiting
"$machine" `xx_do_compute_timeout $timeout` \
419 "$negtext" "$text" $text_is_empty true
422 xx_debug
"xx_do_complex_text_waiting = $res"
429 if $text_is_empty; then
430 xx_fatal
"Command timed-out."
432 xx_fatal
"Failed to match '$text'."
436 if [ -n "$error_msg" ]; then
437 xx_fatal
"$error_msg"
439 xx_fatal
"Command failed."
443 xx_fatal
"Internal error, we shall never reach this line."
451 echo "Usage: $1 [options] scenario-file [scenarios-file ...]"
455 --help Print this help and exit.
456 --headless Hide the screen of the virtual machine.
457 --root=DIR Find HelenOS image in the source directory.
458 --image=FILE File with main HelenOS image (specify --arch).
459 --arch=ARCH Architecture of the image file (see --image).
460 --no-kvm Do not try to run QEMU with KVM enabled.
461 --fail-fast Exit with first error.
462 --debug Print (a lot of) debugging messages.
468 XX_MY_HOME
=`which -- "$0" 2>/dev/null`
469 # Maybe, we are running Bash
470 [ -z "$XX_MY_HOME" ] && XX_MY_HOME
=`which -- "$BASH_SOURCE" 2>/dev/null`
471 XX_MY_HOME
=`dirname -- "$XX_MY_HOME"`
478 XX_TEMP
="$PWD/tmp-vm/"
480 XX_AUTODETECT_HELENOS
=false
487 XX_LAST_MACHINE
=default
490 # Replace with getopt eventually.
491 while [ $# -gt 0 ]; do
501 XX_TRAIN_OCR_FILE
=`echo "$1" | cut '-d=' -f 2-`
507 XX_ACTIVITY_ECHO
=echo
510 XX_HELENOS_ROOT
=`echo "$1" | cut '-d=' -f 2-`
511 XX_AUTODETECT_HELENOS
=true
514 XX_CDROM_FILE
=`echo "$1" | cut '-d=' -f 2-`
515 XX_AUTODETECT_HELENOS
=false
518 XX_ARCH
=`echo "$1" | cut '-d=' -f 2-`
519 XX_AUTODETECT_HELENOS
=false
522 XX_TEMP
=`echo "$1" | cut '-d=' -f 2-`
531 xx_do_print_help
"$0"
535 xx_fatal
"Unknown option $1."
546 if $XX_AUTODETECT_HELENOS; then
547 if ! [ -r "$XX_HELENOS_ROOT/Makefile.config" ]; then
548 xx_fatal
"Cannot open $XX_HELENOS_ROOT/Makefile.config."
550 XX_ARCH
=`grep '^PLATFORM = ' "$XX_HELENOS_ROOT/Makefile.config" | cut '-d=' -f 2- | tr -d ' '`
551 XX_CDROM_FILE
=$XX_HELENOS_ROOT/`cat "$XX_HELENOS_ROOT/defaults/$XX_ARCH/output"`
555 if $XX_TRAIN_OCR; then
556 if [ -z "$XX_TRAIN_OCR_FILE" ]; then
557 echo "Usage notes for --train-ocr command-line switch."
559 echo "Prepare HelenOS image.iso with train-ocr.txt in its root."
561 echo "Run this script with the ISO and with --train-ocr=out.sed"
562 echo "where out.sed is output file with OCR commands."
564 echo "If the script finishes with no error you can replace the"
565 echo "old OCR scheme (ocr.sed) with the new one in out.sed."
570 # Set to false to debug the script below without running QEMU
573 xx_start_machine name
=ocr vterm
=false
574 xx_echo
"Will display the training set on the VM screen"
575 xx_echo
"(this will take a while)."
577 xx_do_type ocr
"cat train-ocr.txt"
579 xx_do_qemu_command ocr
"sendkey ret"
581 xx_do_screenshot ocr
"$XX_TEMP/ocr-full.ppm" "$XX_TEMP/ocr-term.png"
585 xx_echo
"Doing OCR..."
586 xx_do_ocr_prepare
"$XX_TEMP/ocr-term.png" \
592 while read current_line
; do
593 if [ $counter -gt 5 ]; then
594 while read current_line
; do
595 if ! [ "$current_line" = "$prev1_line" -o "$current_line" = "$prev2_line" ]; then
599 alphabet
=`sed -n 's#^\(.\)\(.\)\(\1\2\)\{4,\}##p' train-ocr.txt`
600 alphabet_size
=`echo "$alphabet" | wc -c`
601 for i
in `seq 1 $(( $alphabet_size - 1))`; do
602 symbol
="`echo \"$alphabet\" | fold -w 1 | sed -n \"${i}p\" | sed 's#[*\.\&\\:\/\[]\|\]#\\\\&#g'`"
603 echo "s:$current_line:$symbol:"
606 echo "$current_line" |
tr 'F' '0' |
sed 's#.*#s:&:_:#'
607 echo "$current_line" |
tr '0F' '.' |
sed 's#.*#s:&:?:#'
610 if [ "$current_line" = "$prev2_line" -a "$prev1_line" = "$prev3_line" -a "$prev1_line" != "$prev2_line" ]; then
611 counter
=$
(( $counter + 1 ))
615 prev3_line
="$prev2_line"
616 prev2_line
="$prev1_line"
617 prev1_line
="$current_line"
618 done >"$XX_TRAIN_OCR_FILE"
620 xx_echo
"New OCR scheme is in $XX_TRAIN_OCR_FILE."
625 XX_RESULT_SUMMARY
="$XX_TEMP/summary.$$.txt"
627 date '+# Execution started on %Y-%m-%d %H:%M.' >"$XX_RESULT_SUMMARY"
628 echo '# =======================================' >>"$XX_RESULT_SUMMARY"
633 echo "# Starting scenario $i..."
637 if [ $?
-eq 0 ]; then
642 echo "# Scenario $i terminated, $res."
643 printf '%-35s %4s\n' "$i" "$res" >>"$XX_RESULT_SUMMARY"
644 if $XX_FAIL_FAST && [ "$res" = "FAIL" ]; then
650 date '+# Execution finished on %Y-%m-%d %H:%M.' >>"$XX_RESULT_SUMMARY"
652 # Display the results.
655 cat "$XX_RESULT_SUMMARY"