Update Readme
[ci.git] / test-in-vm.sh
blob8e69b96c704fc635a36ce0136ea25419027d7d99
1 #!/bin/bash
4 # Copyright (c) 2016 Vojtech Horky
5 # All rights reserved.
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
9 # are met:
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
37 xx_echo() {
38 echo ">>" "$@"
41 xx_echo2() {
42 echo " -" "$@"
45 xx_debug() {
46 $XX_DEBUG_ECHO " :" "$@"
49 xx_debug_filter() {
50 sed 's#.*# : &#'
54 xx_run_debug() {
55 xx_debug "$@"
56 "$@"
59 xx_activity() {
60 $XX_ACTIVITY_ECHO " +" "$@"
63 xx_fatal() {
64 echo "!!" "$@"
65 xx_shutdown
68 ## Prepares pipe to QEMU monitor.
69 # Feed QEMU commands to stdin of this function.
71 # Do not forget to check for return code as checking it inside pipe
72 # is not very useful (we cannot terminate the script from that).
74 # @param 1 Machine id.
75 xx_do_qemu_command_pipe() {
76 socat STDIN "UNIX-CONNECT:$XX_TEMP/$1.monitor"
79 ## Sends command to QEMU monitor.
80 # This function terminates the scenario on failure of the monitoring pipe.
82 # @param 1 Machine id.
83 # @param 2 Command to send.
84 xx_do_qemu_command() {
85 xx_debug "echo '$2' | socat STDIN 'UNIX-CONNECT:$XX_TEMP/$1.monitor'"
86 echo "$2" | socat STDIN "UNIX-CONNECT:$XX_TEMP/$1.monitor"
87 res=$?
88 if [ $res -ne 0 ] && [ "$3" != "nodie" ]; then
89 xx_shutdown
93 ## Types the text to a specific machine.
95 # @param 1 Machine id.
96 # @param 2 Text to type.
97 xx_do_type() {
98 ( echo "$2" | fold -w 1 | sed \
99 -e 's/ /spc/' \
100 -e 's/\./dot/' \
101 -e 's/-/minus/' \
102 -e 's/+/shift-equal/' \
103 -e 's/_/shift-minus/' \
104 -e 's/@/shift-2/' \
105 -e 's/:/shift-semicolon/' \
106 -e 's/\//slash/' \
107 -e 's/=/equal/' \
108 -e 's/|/shift-backslash/' \
109 -e 's/\([[:upper:]]\)/shift-\L\1/' \
110 -e 's/\\/backslash/' | \
111 while read code; do
112 xx_do_qemu_command "$1" "sendkey $code"
113 done
115 res=$?
116 if [ $res -ne 0 ]; then
117 xx_shutdown
121 xx_get_var() {
122 local varname="$1"
123 local default="$2"
124 shift 2
125 local i
126 for i in "$@"; do
127 case $i in
128 $varname=*)
129 echo "$i" | cut '-d=' -f 2-
130 return
134 esac
135 done
136 echo "$default"
139 xx_get_def_var() {
140 local i
141 for i in "$@"; do
142 echo "$i" | grep -v '^[a-zA-Z][a-zA-Z0-9_]*='
143 done
144 echo
147 xx_get_boolean_var() {
148 value=`xx_get_var "$@" | tr 'A-Z' 'a-z'`
149 if [ "$value" = "yes" -o "$value" = "true" ]; then
150 echo "true"
151 elif [ "$value" = "no" -o "$value" = "false" ]; then
152 echo "false"
153 else
154 echo
159 xx_shutdown() {
160 local i
161 for i in $XX_KNOWN_MACHINES; do
162 xx_stop_machine name="$i"
163 done
164 exit 1
167 xx_assert_var() {
168 if [ -z "$2" ]; then
169 xx_echo "Option $1 not specified or invalid, terminating."
170 xx_shutdown
174 xx_do_check_var() {
175 if [ -z "$2" ]; then
176 xx_fatal "Option $1 not specified with no suitable default."
178 if [ -n "$3" ]; then
179 if ! echo "$2" | grep -q "$3"; then
180 xx_fatal "Option $1 does not match '$3' (got '$2')."
185 xx_do_compute_timeout() {
186 echo $(( `date +%s` + 10 * $1 ))
190 xx_start_machine() {
191 local cdrom=`xx_get_var cdrom "$XX_CDROM_FILE" "$@"`
192 local name=`xx_get_var name default "$@"`
193 local wait_for_vterm=`xx_get_boolean_var vterm true "$@"`
195 xx_do_check_var "xx_start_machine/name" "$name" '^[a-zA-Z][0-9a-zA-Z]*$'
196 xx_do_check_var "xx_start_machine/cdrom" "$cdrom"
198 local extra_opts=""
199 if $XX_HEADLESS; then
200 extra_opts="-display none"
203 xx_echo "Starting machine $name from $cdrom."
205 local qemu_command=""
206 local qemu_opts=""
207 local qemu_common_network_options="-device e1000,vlan=0 \
208 -net user,hostfwd=udp::8080-:8080,hostfwd=udp::8081-:8081,hostfwd=tcp::8080-:8080,hostfwd=tcp::8081-:8081,hostfwd=tcp::2223-:2223"
209 local has_grub=false
210 if [ "$XX_ARCH" == "ia32" ]; then
211 qemu_command=qemu-system-i386
212 qemu_opts="$qemu_common_network_options -usb -boot d -cdrom $cdrom"
213 if $XX_USE_KVM; then
214 extra_opts="$extra_opts -enable-kvm"
216 has_grub=true
217 elif [ "$XX_ARCH" == "amd64" ]; then
218 qemu_command=qemu-system-x86_64
219 qemu_opts="$qemu_common_network_options -usb -boot d -cdrom $cdrom"
220 if $XX_USE_KVM; then
221 extra_opts="$extra_opts -enable-kvm"
223 has_grub=true
224 elif [ "$XX_ARCH" == "arm32/integratorcp" ]; then
225 qemu_command=qemu-system-arm
226 qemu_opts="-M integratorcp -usb -kernel $cdrom"
227 elif [ "$XX_ARCH" == "ppc32" ]; then
228 qemu_command=qemu-system-ppc
229 qemu_opts="$qemu_common_network_options -usb -boot d -cdrom $cdrom"
231 if [ -z "$qemu_command" ]; then
232 xx_fatal "Unable to find proper emulator."
235 xx_run_debug $qemu_command \
236 $extra_opts \
237 $qemu_opts \
238 -m "${XX_MEMORY_MB}" \
239 -daemonize -pidfile "$XX_TEMP/$name.pid" \
240 -monitor "unix:$XX_TEMP/$name.monitor,server,nowait"
241 sleep 1
243 if $has_grub; then
244 xx_do_qemu_command "$name" "sendkey ret"
247 XX_LAST_MACHINE=$name
249 XX_KNOWN_MACHINES="$XX_KNOWN_MACHINES $name"
251 if $wait_for_vterm; then
252 xx_echo2 "Waiting for OS to boot into GUI..."
253 if ! xx_do_wait_for_text "$name" `xx_do_compute_timeout 60` "to see a few survival tips"; then
254 xx_fatal "OS have not booted into a known state."
262 xx_stop_machine() {
263 local name=`xx_get_var name $XX_LAST_MACHINE "$@"`
265 xx_echo "Forcefully killing machine $name."
266 if [ -e "$XX_TEMP/$name.monitor" ]; then
267 xx_do_qemu_command "$name" "quit" nodie
269 sleep 1
270 kill -9 `cat "$XX_TEMP/$name.pid" 2>/dev/null` 2>/dev/null
272 if [ "$name" = "$XX_LAST_MACHINE" ]; then
273 XX_LAST_MACHINE=""
278 ## Wait for text to appear on machine console.
280 # @param 1 Machine id.
281 # @param 2 UNIX end-time (date %s + TIME_OUT).
282 # @param 3 Text to match.
283 xx_do_wait_for_text() {
284 while true; do
285 xx_activity "Taking screenshot, looking for '$3'."
287 xx_do_screenshot "$1" "$XX_TEMP/$1-full.ppm" \
288 "$XX_TEMP/$1-term.png" "$XX_TEMP/$1-term.txt"
290 if grep -q "$3" <"$XX_TEMP/$1-term.txt"; then
291 return 0
294 if [ `date +%s` -gt $2 ]; then
295 return 1
298 sleep 1
299 done
302 xx_assert() {
303 local timeout=`xx_get_var timeout $XX_DEFAULT_TIMEOUT "$@"`
304 local machine=`xx_get_var machine $XX_LAST_MACHINE "$@"`
305 local error_msg=`xx_get_var error "" "$@"`
306 local text=`xx_get_def_var "$@"`
308 xx_echo "Checking that '$text' will appear on $machine within ${timeout}s."
310 if ! xx_do_wait_for_text "$machine" `xx_do_compute_timeout $timeout` "$text"; then
311 xx_fatal "Failed to recognize '$text' on $machine."
315 xx_die_on() {
316 local timeout=`xx_get_var timeout $XX_DEFAULT_TIMEOUT "$@"`
317 local machine=`xx_get_var machine $XX_LAST_MACHINE "$@"`
318 local error_msg=`xx_get_var message "" "$@"`
319 local text=`xx_get_def_var "$@"`
321 xx_echo "Checking that '$text' will not appear on $machine within ${timeout}s."
323 if xx_do_wait_for_text "$machine" `xx_do_compute_timeout $timeout` "$text"; then
324 xx_fatal "Prohibited text '$text' spotted on $machine."
328 xx_sleep() {
329 local amount=`xx_get_def_var "$@"`
331 xx_echo "Waiting for ${amount}s."
332 sleep $amount
336 xx_cls() {
337 local machine=`xx_get_var machine $XX_LAST_MACHINE "$@"`
339 xx_echo "Clearing the screen on $machine."
340 for i in `seq 1 35`; do
341 xx_do_qemu_command "$machine" "sendkey ret"
342 done
343 sleep 1
346 xx_do_ocr_prepare() {
347 local image_width=`identify -format '%w' "$1"`
348 local image_height=`identify -format '%h' "$1"`
349 local correct_size="$(( ( $image_width / 8 ) * 8 ))x$(( ( $image_height / 16 ) * 16 ))"
350 convert "$1" -crop "$correct_size" +repage -crop "8x16" +repage +adjoin txt:- \
351 | sed -e 's|[0-9]*,[0-9]*: ([^)]*)[ ]*#\([0-9A-Fa-f]\{6\}\).*|\1|' -e 's:^#.*:@:' -e 's#000000#0#g' -e 's#FFFFFF#F#' \
352 | sed -e ':a' -e 'N;s#\n##;s#^@##;/@$/{s#@$##p;d}' -e 't a'
355 xx_do_ocr() {
356 local column_count="$(( `identify -format '%w' $1` / 8 ))"
357 local row_count="$(( `identify -format '%h' $1` / 16 ))"
358 # What we do in this magick pipeline:
359 # - convert the image to text, i.e. each pixel's color is converted to 0 or F (black/white)
360 # - do actual OCR - known sequences of pixels are replaced with single letter
361 # - replace unrecognized sequences with question mark (the line shall contain one letter only)
362 # - paste the character to form one long line
363 # - fold the line to get back the original terminal
364 # - keep only the first lines
365 xx_do_ocr_prepare "$1" \
366 | sed -f "$XX_MY_HOME/ocr.sed" \
367 | sed '/../s#.*#?#' \
368 | paste -sd '' \
369 | fold -w "$column_count" \
370 | head -n "$row_count"
373 xx_do_screenshot() {
374 xx_do_qemu_command "$1" "screendump $2"
375 if [ -n "$3" ]; then
376 # Looping as the screenshot might take some time to save
377 local retries=3
378 while true; do
379 if convert "$2" -crop 640x480+4+24 +repage -colors 2 -monochrome "$3"; then
380 break
382 retries=$(( $retries - 1 ))
383 if [ $retries -le 0 ]; then
384 xx_fatal "Failed to convert screen to monochrome."
386 sleep 1
387 done
389 if [ -n "$4" ]; then
390 xx_do_ocr "$3" >"$4"
391 if $XX_DUMP_TERMINAL; then
392 local print_it=false
393 if [ -e "$4.previous" ]; then
394 if ! cmp --quiet "$4.previous" "$4"; then
395 print_it=true
397 else
398 print_it=true
401 if $print_it; then
402 xx_debug "Terminal content:"
403 sed 's#.*# | &#' <"$4" | xx_debug_filter
405 cat "$4" >"$4.previous"
411 ## Wait for text to appear on machine console.
413 # @param 1 Machine id.
414 # @param 2 UNIX end-time (date %s + TIME_OUT).
415 # @param 3 Text that shall not be matched.
416 # @param 4 Text that shall be matched before timeout.
417 # @param 5 Consider prompt as a success.
418 # @param 6 Check for standard Bdsh error messages.
419 # @retval 0 Expected text was matched.
420 # @retval 1 Prompt text was matched.
421 # @retval 2 Time-out, nothing matched.
422 # @retval 3 Unexpected text was matched.
423 # @retval 4 Standard Bdsh error message detected.
424 xx_do_complex_text_waiting() {
425 xx_debug "waiting: $1/$2 match='$4', no-match='$3'"
426 xx_debug "waiting: $1/$2 prompt_is_success=$5 check_for_bdsh_error=$6"
428 while true; do
429 xx_activity "Taking screenshot, checking for specific output."
431 xx_do_screenshot "$1" "$XX_TEMP/$1-full.ppm" \
432 "$XX_TEMP/$1-term.png" "$XX_TEMP/$1-term.txt"
434 if [ -n "$3" ] && grep -q "$3" <"$XX_TEMP/$1-term.txt"; then
435 return 3
438 if [ -n "$4" ] && grep -q "$4" <"$XX_TEMP/$1-term.txt" ; then
439 return 0
442 if $6 && grep -q -e 'Cannot spawn' -e 'Command failed' <"$XX_TEMP/$1-term.txt"; then
443 return 4
446 if $5 && grep -q '^/[-a-zA-Z0-9_/.]* # _[ ]*$' <"$XX_TEMP/$1-term.txt" ; then
447 return 0
450 if [ `date +%s` -gt $2 ]; then
451 return 2
454 sleep 1
455 done
458 xx_cmd() {
459 local cmd=`xx_get_def_var "$@"`
460 local timeout=`xx_get_var timeout $XX_DEFAULT_TIMEOUT "$@"`
461 local machine=`xx_get_var machine $XX_LAST_MACHINE "$@"`
462 local error_msg=`xx_get_var error "" "$@"`
463 local text=`xx_get_var assert "" "$@"`
464 local negtext=`xx_get_var die_on "" "$@"`
465 local text_is_empty=`if [ -n "$text" ]; then echo false; else echo true; fi`
467 xx_echo "Sending '$cmd' to $machine."
468 xx_do_type "$machine" "$cmd"
469 xx_do_qemu_command "$machine" "sendkey ret"
472 xx_do_complex_text_waiting "$machine" `xx_do_compute_timeout $timeout` \
473 "$negtext" "$text" $text_is_empty true
474 local res=$?
476 xx_debug "xx_do_complex_text_waiting = $res"
478 case $res in
479 0|1)
480 return 0
483 if $text_is_empty; then
484 xx_fatal "Command timed-out."
485 else
486 xx_fatal "Failed to match '$text'."
489 3|4)
490 if [ -n "$error_msg" ]; then
491 xx_fatal "$error_msg"
492 else
493 xx_fatal "Command failed."
497 xx_fatal "Internal error, we shall never reach this line."
499 esac
504 xx_do_print_help() {
505 echo "Usage: $1 [options] scenario-file [scenarios-file ...]"
506 cat <<'EOF_USAGE'
507 where [options] are:
509 --help Print this help and exit.
510 --headless Hide the screen of the virtual machine.
511 --root=DIR Find HelenOS image in the source directory.
512 --image=FILE File with main HelenOS image (specify --arch).
513 --arch=ARCH Architecture of the image file (see --image).
514 --no-kvm Do not try to run QEMU with KVM enabled.
515 --memory=INT Size of VM RAM in INT MB.
516 --fail-fast Exit with first error.
517 --debug Print (a lot of) debugging messages.
518 --activity Print messages about background activity.
519 --dump-terminal Print contents of the vterm (implies --debug).
520 --train-ocr For experts: train OCR for vterm contents.
522 EOF_USAGE
526 XX_MY_HOME=`which -- "$0" 2>/dev/null`
527 # Maybe, we are running Bash
528 [ -z "$XX_MY_HOME" ] && XX_MY_HOME=`which -- "$BASH_SOURCE" 2>/dev/null`
529 XX_MY_HOME=`dirname -- "$XX_MY_HOME"`
531 XX_DEBUG_ECHO=:
532 XX_ACTIVITY_ECHO=:
533 XX_DUMP_TERMINAL=false
534 XX_TRAIN_OCR=false
535 XX_HEADLESS=false
536 XX_USE_KVM=true
537 XX_TEMP="$PWD/tmp-vm/"
538 XX_HELENOS_ROOT="."
539 XX_AUTODETECT_HELENOS=false
540 XX_ARCH=""
541 XX_CDROM_FILE=""
542 XX_MEMORY_MB=""
543 XX_FAIL_FAST=false
545 XX_KNOWN_MACHINES=""
546 XX_DEFAULT_TIMEOUT=5
547 XX_LAST_MACHINE=default
550 # Replace with getopt eventually.
551 while [ $# -gt 0 ]; do
552 case "$1" in
553 --headless)
554 XX_HEADLESS=true
556 --train-ocr)
557 XX_TRAIN_OCR=true
559 --train-ocr=*)
560 XX_TRAIN_OCR=true
561 XX_TRAIN_OCR_FILE=`echo "$1" | cut '-d=' -f 2-`
563 --debug)
564 XX_DEBUG_ECHO=echo
566 --activity)
567 XX_ACTIVITY_ECHO=echo
569 --dump-terminal)
570 XX_DUMP_TERMINAL=true
571 XX_DEBUG_ECHO=echo
573 --root=*)
574 XX_HELENOS_ROOT=`echo "$1" | cut '-d=' -f 2-`
575 XX_AUTODETECT_HELENOS=true
577 --image=*)
578 XX_CDROM_FILE=`echo "$1" | cut '-d=' -f 2-`
579 XX_AUTODETECT_HELENOS=false
581 --arch=*)
582 XX_ARCH=`echo "$1" | cut '-d=' -f 2-`
583 XX_AUTODETECT_HELENOS=false
585 --memory=*)
586 XX_MEMORY_MB=`echo "$1" | cut '-d=' -f 2-`
587 if ! echo "$XX_MEMORY_MB" | grep -q '^[1-9][0-9]*$'; then
588 xx_fatal "--memory has to be provided as an integer."
591 --temp=*)
592 XX_TEMP=`echo "$1" | cut '-d=' -f 2-`
594 --no-kvm)
595 XX_USE_KVM=false
597 --fail-fast)
598 XX_FAIL_FAST=true
600 --help|-h|-?)
601 xx_do_print_help "$0"
602 exit 0
604 --*)
605 xx_fatal "Unknown option $1."
608 break
610 esac
611 shift
612 done
614 [ -z "$XX_MEMORY_MB" ] && XX_MEMORY_MB=256
616 mkdir -p "$XX_TEMP"
618 if $XX_AUTODETECT_HELENOS; then
619 if ! [ -r "$XX_HELENOS_ROOT/Makefile.config" ]; then
620 xx_fatal "Cannot open $XX_HELENOS_ROOT/Makefile.config."
622 XX_ARCH=`grep '^PLATFORM = ' "$XX_HELENOS_ROOT/Makefile.config" | cut '-d=' -f 2- | tr -d ' '`
623 XX_CDROM_FILE=$XX_HELENOS_ROOT/`cat "$XX_HELENOS_ROOT/defaults/$XX_ARCH/output"`
627 if $XX_TRAIN_OCR; then
628 if [ -z "$XX_TRAIN_OCR_FILE" ]; then
629 echo "Usage notes for --train-ocr command-line switch."
630 echo
631 echo "Prepare HelenOS image.iso with train-ocr.txt in its root."
632 echo
633 echo "Run this script with the ISO and with --train-ocr=out.sed"
634 echo "where out.sed is output file with OCR commands."
635 echo
636 echo "If the script finishes with no error you can replace the"
637 echo "old OCR scheme (ocr.sed) with the new one in out.sed."
638 echo
639 exit
642 # Set to false to debug the script below without running QEMU
643 # all the time
644 if true; then
645 xx_start_machine name=ocr vterm=false
646 xx_echo "Will display the training set on the VM screen"
647 xx_echo "(this will take a while)."
648 sleep 20
649 xx_do_type ocr "cat train-ocr.txt"
650 sleep 1
651 xx_do_qemu_command ocr "sendkey ret"
652 sleep 5
653 xx_do_screenshot ocr "$XX_TEMP/ocr-full.ppm" "$XX_TEMP/ocr-term.png"
654 xx_stop_machine
657 xx_echo "Doing OCR..."
658 xx_do_ocr_prepare "$XX_TEMP/ocr-term.png" \
660 prev1_line=""
661 prev2_line=""
662 prev3_line=""
663 counter=0
664 while read current_line; do
665 if [ $counter -gt 5 ]; then
666 while read current_line; do
667 if ! [ "$current_line" = "$prev1_line" -o "$current_line" = "$prev2_line" ]; then
668 break
670 done
671 alphabet=`sed -n 's#^\(.\)\(.\)\(\1\2\)\{4,\}##p' train-ocr.txt`
672 alphabet_size=`echo "$alphabet" | wc -c`
673 for i in `seq 1 $(( $alphabet_size - 1))`; do
674 symbol="`echo \"$alphabet\" | fold -w 1 | sed -n \"${i}p\" | sed 's#[*\.\&\\:\/\[]\|\]#\\\\&#g'`"
675 echo "s:$current_line:$symbol:"
676 read current_line
677 done
678 echo "$current_line" | tr 'F' '0' | sed 's#.*#s:&:_:#'
679 echo "$current_line" | tr '0F' '.' | sed 's#.*#s:&:?:#'
680 break
682 if [ "$current_line" = "$prev2_line" -a "$prev1_line" = "$prev3_line" -a "$prev1_line" != "$prev2_line" ]; then
683 counter=$(( $counter + 1 ))
684 else
685 counter=0
687 prev3_line="$prev2_line"
688 prev2_line="$prev1_line"
689 prev1_line="$current_line"
690 done >"$XX_TRAIN_OCR_FILE"
692 xx_echo "New OCR scheme is in $XX_TRAIN_OCR_FILE."
693 exit
697 XX_RESULT_SUMMARY="$XX_TEMP/summary.$$.txt"
699 date '+# Execution started on %Y-%m-%d %H:%M.' >"$XX_RESULT_SUMMARY"
700 echo '# =======================================' >>"$XX_RESULT_SUMMARY"
703 # Run it
704 for i in "$@"; do
705 echo "# Starting scenario $i..."
707 . $i
708 for i in $XX_KNOWN_MACHINES; do
709 xx_stop_machine name="$i"
710 done
712 if [ $? -eq 0 ]; then
713 res="OK"
714 else
715 res="FAIL"
717 echo "# Scenario $i terminated, $res."
718 printf '%-35s %4s\n' "$i" "$res" >>"$XX_RESULT_SUMMARY"
719 if $XX_FAIL_FAST && [ "$res" = "FAIL" ]; then
720 exit 1
722 done
725 date '+# Execution finished on %Y-%m-%d %H:%M.' >>"$XX_RESULT_SUMMARY"
727 # Display the results.
728 echo
730 cat "$XX_RESULT_SUMMARY"