Guide 3. Guest Additions

What you're going to learn

In this guide you're going to learn:

  1. Test hierarchy basics.
  2. Testo Guest Additions installation routine.

Preconditions

  1. Testo Framework is installed.
  2. Virt manager is installed.
  3. Ubuntu server 16.04 image is downloaded and located here: /opt/iso/ubuntu_server.iso. The location may be different, but in this case the test scripts have to be adjusted accordingly.
  4. Testo guest additions iso image is downloaded and located in the same folder as Ubuntu Server 16.04 iso-image.
  5. (Recommended) Testo-lang syntax highlight for Sublime Text 3 is set up.
  6. (Recommended) Guide 2 is complete.

Introduction

In the last guide we've learned how to automate the Ubuntu Server 16.04 installation and got ourselved acquainted with the basic actions in Testo-lang mimicking the human behaviour. The approach we've used is good from two points of view:

  1. Allows to test the SUT in exactly the same way as the end user (real human) would've tested it.
  2. Doesn't require any additional agents running in virtual machines.

However, there is a downside to this approach as well: confusing and not intuitive console command execution. Indeed, to execute a bash command using a human-mimicking approach you'd need to do something like that:

type "command_to_execute";
press Enter
type "echo Result is $?"; press Enter
wait "Result is 0"

Of course, that's not what you want to see in your test scripts. Much simpler would be to use something like this:

exec bash "command_to_execute"

and just rely on the exit code of the bash script.

To overcome this problem, the Testo Framework comes with the Guest Additions iso-image. The Guest Additions support various guest operating systems, including, but not limited to: Linux-based Ubuntu and CentOS, as well as Windows 7 and 10. When the Guest Additions are installed in the virtual machine, you unlock new high-level actions in your test scripts: exec - execute a script (bash, python or cmd) on the virtual machine, copyto - copy files from the Host to the guest and copyfrom - copy files from the guest to the Host.

Guest Additions are recommended for installation in a virtual machine in two cases:

  1. If the virtual machine is secondary and you don't care much about it.
  2. If the guest additions wouldn't affect the software under test behaviour.

To sum everything up, you should install Guest Addiitons in the virtual machines in most cases and discard them only when the additions are not installable or just undesired.

What to begin with?

To install the guest additions in an already installed guest OS, you need to perform several easy steps:

  1. Plug the guest additions iso image into the virtual DVD-drive.
  2. Mount the plugged iso image into the guest file system (if not done automatically).
  3. Run the guest additions installation (may differ depending on the guest OS).

Let's go back to the test script we've developed in the Guide 2, in which we automated the Ubuntu Server 16.04 installation.

Let's begin with renaming the test my_first_test into something more appropriate. For example, ubuntu_installation.

test ubuntu_installation {
    my_ubuntu {
        start
        wait "English"
        ...
        wait "login:" timeout 2m; type "my-ubuntu-login"; press Enter
        wait "Password:"; type "1111"; press Enter
        wait "Welcome to Ubuntu"
    }
}

Don't run the new script just yet, we'll do that in a jiffy. Right now let's proceed to the guest additions installation automation.

But first we need to learn what the tests hiearchy is. All the tests are organized in a tree based on the "testing from the simple to the complex" concept. Tests can have a "parent-children" connection, where the "simpler" test plays the role of the parent and the "more complex" test plays the role of the child. A test may have multiple parents and multiple children. A child is not going run until all his parents are completed successfully. The most simple tests that don't depend on anything (and have no parents) are called base tests, otherwise a test is called derived. In our guides the ubuntu_installation test is an example of a base test.

The "child-parent" connection is created like this:

test guest_additions_installation: ubuntu_installation {
    my_ubuntu {
        abort "stop here"
    }
}

Let's make sure that a child test really depends on the parent test.

To do that, let's run our script file, but this time we're going to use a new command-line argument --test_spec. This argument specifies the tests we want to run (instead of running all of the tests). After the run we'll see something like this:

user$ sudo testo run ~/testo/hello_world.testo --stop_on_fail --test_spec guest_additions_installation
TESTS TO RUN:
ubuntu_installation
guest_additions_installation
[ 0%] Preparing the environment for test ubuntu_installation
[ 0%] Restoring snapshot initial for virtual machine my_ubuntu
[ 0%] Running test ubuntu_installation
[ 0%] Starting virtual machine my_ubuntu
[ 0%] Waiting English for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Install Ubuntu Server for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Choose the language for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Select your location for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Detect keyboard layout? for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Country of origin for the keyboard for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Keyboard layout for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting No network interfaces detected for 5m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Hostname: for 30s with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key BACKSPACE 36 times on virtual machine my_ubuntu
[ 0%] Typing "my-ubuntu" with interval 30ms in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Full name for the new user for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Typing "my-ubuntu-login" with interval 30ms in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Username for your account for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Choose a password for the new user for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Typing "1111" with interval 30ms in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Re-enter password to verify for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Typing "1111" with interval 30ms in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Use weak password? for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key LEFT on virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Encrypt your home directory? for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Select your timezone for 2m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Partitioning method for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Select disk to partition for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Write the changes to disks and configure LVM? for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key LEFT on virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Amount of volume group to use for guided partitioning for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Write the changes to disks? for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key LEFT on virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting HTTP proxy information for 3m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting How do you want to manage upgrades for 6m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Choose software to install for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Install the GRUB boot loader to the master boot record? for 10m with interval 1s in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Installation complete for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Unplugging dvd from virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting login: for 2m with interval 1s in virtual machine my_ubuntu
[ 0%] Typing "my-ubuntu-login" with interval 30ms in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Password: for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Typing "1111" with interval 30ms in virtual machine my_ubuntu
[ 0%] Pressing key ENTER on virtual machine my_ubuntu
[ 0%] Waiting Welcome to Ubuntu for 1m with interval 1s in virtual machine my_ubuntu
[ 0%] Taking snapshot ubuntu_installation for virtual machine my_ubuntu
[ 50%] Test ubuntu_installation PASSED in 0h:4m:22s
[ 50%] Preparing the environment for test guest_additions_installation
[ 50%] Running test guest_additions_installation
/home/alex/testo/hello_world.testo:52:3: Caught abort action on virtual machine my_ubuntu with message: stop here
[100%] Test guest_additions_installation FAILED in 0h:0m:0s
user$

At the begin of the output we can see, that the Testo Framework is shceduled the ubuntu_installation test to run, despite the fact that we only asked to run the guest_additions_installation test. It happened because we want to run a child test, but the parent test hasn't run successfully just yet. And therefore, Testo Framework automatically queues the parent test first, and only after that - the child test.

But haven't we already installed Ubuntu successfully? We ended the previous guide at the moment when the Ubuntu Server was installed, the test ended, and the virtual machine state must had been staged.

In fact, in the previous guide our test had the name my_first_test, and now, with the new name, Testo Framework sees it as a brand new test, which has never run before.

At the end of the output we can see, that the parent test has been run successfully, and Testo proceeded to the child test run, but it failed (because of the abort action).

If we run Testo one more time with the same arguments, we will see a new picture:

user$ sudo testo run ~/testo/hello_world.testo --stop_on_fail --test_spec guest_additions_installation
UP-TO-DATE TESTS:
ubuntu_installation
TESTS TO RUN:
guest_additions_installation
[ 50%] Preparing the environment for test guest_additions_installation
[ 50%] Restoring snapshot ubuntu_installation for virtual machine my_ubuntu
[ 50%] Running test guest_additions_installation
/home/alex/testo/hello_world.testo:52:3: Caught abort action on virtual machine my_ubuntu with message: stop here
[100%] Test guest_additions_installation FAILED in 0h:0m:0s
user$

This means that Testo Framework detected, that the ubuntu_installation test had already run successfully, and its virtual machine state had been staged. And so, instead of running the parent test again, Testo was able to just rollback the test bench into the state it was at the end of ubuntu_installation test.

Installing the Guest Additions

Now it's time to automate the guest additions installation. In the last guide we've learned about the unplug dvd action, which "ejects" the mounted iso-image from the DVD-drive of the virtual machine. Naturally, there is a plug dvd action in Testo-lang, allowing you to "insert" an iso-image to the DVD-drive. This action, however, takes an argument - a path to the iso-image you want to "insert".

test guest_additions_installation: ubuntu_installation {
    my_ubuntu {
        plug dvd "/opt/iso/testo-guest-additions.iso"
        abort "stop here"
    }
}

Try to run this script (don't forget the --sto_on_fail command line argument), wait for the breakpoint to trigger and open the virtual machine properties with the virt-manager. In the CDROM section you'll find the information about the iso-image you've just plugged.

CDROM plugged

Now we need to mount the DVD-drive into the Ubuntu filesystem. Since we'd need the root privileges to do that, let's enter the sudo mode first.

test guest_additions_installation: ubuntu_installation {
    my_ubuntu {
        plug dvd "/opt/iso/testo-guest-additions.iso"

        type "sudo su"; press Enter;
        wait "password for my-ubuntu-login"; type "1111"; press Enter
        wait "root@my-ubuntu"

        abort "stop here"
    }
}

And now, finally, let's install the guest additions deb-package. Don't forget to umount the DVD-drive and "eject" the iso from the DVD-drive after that.

test guest_additions_installation: ubuntu_installation {
    my_ubuntu {
        plug dvd "/opt/iso/testo-guest-additions.iso"

        type "sudo su"; press Enter;
        wait "password for my-ubuntu-login"; type "1111"; press Enter
        wait "root@my-ubuntu"

        type "mount /dev/cdrom /media"; press Enter
        wait "mounting read-only"; type "dpkg -i /media/*.deb"; press Enter;
        wait "Setting up testo-guest-additions"
        type "umount /media"; press Enter;
        # Give a little time for the umount to do its job
        sleep 2s
        unplug dvd
    }
}

Take a note, that we used a new action: sleep. This aciton works exactly how you'd exect it to work: just waits unconditionally for the specified amount of time.

You may now remove the abort at the end of the test to complete it.

user$ sudo testo run ~/testo/hello_world.testo --stop_on_fail --test_spec guest_additions_installation
UP-TO-DATE TESTS:
ubuntu_installation
TESTS TO RUN:
guest_additions_installation
[ 50%] Preparing the environment for test guest_additions_installation
[ 50%] Restoring snapshot ubuntu_installation for virtual machine my_ubuntu
[ 50%] Running test guest_additions_installation
[ 50%] Plugging dvd /opt/iso/testo-guest-additions.iso into virtual machine my_ubuntu
[ 50%] Typing "sudo su" with interval 30ms in virtual machine my_ubuntu
[ 50%] Pressing key ENTER on virtual machine my_ubuntu
[ 50%] Waiting password for my-ubuntu-login for 1m with interval 1s in virtual machine my_ubuntu
[ 50%] Typing "1111" with interval 30ms in virtual machine my_ubuntu
[ 50%] Pressing key ENTER on virtual machine my_ubuntu
[ 50%] Waiting root@my-ubuntu for 1m with interval 1s in virtual machine my_ubuntu
[ 50%] Typing "mount /dev/cdrom /media" with interval 30ms in virtual machine my_ubuntu
[ 50%] Pressing key ENTER on virtual machine my_ubuntu
[ 50%] Waiting mounting read-only for 1m with interval 1s in virtual machine my_ubuntu
[ 50%] Typing "dpkg -i /media/*.deb" with interval 30ms in virtual machine my_ubuntu
[ 50%] Pressing key ENTER on virtual machine my_ubuntu
[ 50%] Waiting Setting up testo-guest-additions for 1m with interval 1s in virtual machine my_ubuntu
[ 50%] Typing "umount /media" with interval 30ms in virtual machine my_ubuntu
[ 50%] Pressing key ENTER on virtual machine my_ubuntu
[ 50%] Sleeping in virtual machine my_ubuntu for 2s
[ 50%] Unplugging dvd from virtual machine my_ubuntu
[ 50%] Taking snapshot guest_additions_installation for virtual machine my_ubuntu
[100%] Test guest_additions_installation PASSED in 0h:0m:17s
PROCESSED TOTAL 2 TESTS IN 0h:0m:17s
UP-TO-DATE: 1
RUN SUCCESSFULLY: 1
FAILED: 0
user$

And so the guest additions are installed. They are now up and running, so let's try them out.

Trying out the guest additions

To try out the guest additions, let's create a new test which is going to be a child to the test guest_additions_installation. With the guest additions installed, we are able to use some new high-level acitons. In this guide we're going to focus on the exec action. Now let's try to execute a bash script which prints "Hello world" to the stdout.

test guest_additions_demo: guest_additions_installation {
    my_ubuntu {
        exec bash "echo Hello world"
    }
}

The result:

user$ sudo testo run ~/testo/hello_world.testo --stop_on_fail --test_spec guest_additions_demo
UP-TO-DATE TESTS:
ubuntu_installation
guest_additions_installation
TESTS TO RUN:
guest_additions_demo
[ 67%] Preparing the environment for test guest_additions_demo
[ 67%] Restoring snapshot guest_additions_installation for virtual machine my_ubuntu
[ 67%] Running test guest_additions_demo
[ 67%] Executing bash command in virtual machine my_ubuntu with timeout 10m
+ echo Hello world
Hello world
[ 67%] Taking snapshot guest_additions_demo for virtual machine my_ubuntu
[100%] Test guest_additions_demo PASSED in 0h:0m:4s
PROCESSED TOTAL 3 TESTS IN 0h:0m:4s
UP-TO-DATE: 2
RUN SUCCESSFULLY: 1
FAILED: 0
user$

We can see that the bash script run successfully. As the matter of fact, the exec action is not limited to bash scripts execution. You could also run python srcipts (if Python interpreter is available in the guest system at all). Scripts could be multiline (just encase them in triple quotes).

test guest_additions_demo: guest_additions_installation {
    my_ubuntu {
        exec bash """
            echo Hello world
            echo from bash
        """
        # Double quotes require the escape symbol in one-line strings
        exec python2 "print('Hello from python2!')"
        exec python3 "print('Hello from python3!')"
    }
}
user$ sudo testo run ~/testo/hello_world.testo --stop_on_fail --test_spec guest_additions_demo
Because of the cache loss, Testo is scheduled to run the following tests:
- guest_additions_demo
Do you confirm running them? [y/N]: y
UP-TO-DATE TESTS:
ubuntu_installation
guest_additions_installation
TESTS TO RUN:
guest_additions_demo
[ 67%] Preparing the environment for test guest_additions_demo
[ 67%] Restoring snapshot guest_additions_installation for virtual machine my_ubuntu
[ 67%] Running test guest_additions_demo
[ 67%] Executing bash command in virtual machine my_ubuntu with timeout 10m
+ echo Hello world
Hello world
+ echo from bash
from bash
[ 67%] Executing python3 command in virtual machine my_ubuntu with timeout 10m
Hello from python3!
[ 67%] Taking snapshot guest_additions_demo for virtual machine my_ubuntu
[100%] Test guest_additions_demo PASSED in 0h:0m:5s
PROCESSED TOTAL 3 TESTS IN 0h:0m:5s
UP-TO-DATE: 2
RUN SUCCESSFULLY: 1
FAILED: 0
user$

We will learn other actions that come with the guest additions installation in the future guides. For instance, copyto actions are considered in guide 5.

Conclusion

At the end of this guide we've got the next tests tree.

You can find the complete test scripts for this guide here.