Executing tests in daemon mode

In daemon mode QF-Test listens to RMI connections and provides an interface for remote test execution. This is useful for simplifying test execution in a distributed load-testing scenario (chapter 31), but also for integration with existing test management or test execution tools (chapter 26).

NoteGUI tests require an active user session. Chapter Hints on setting up test systems contains useful tips and tricks to set-up the daemon process. In FAQ 14 you can find technical details.

Launching the daemon

!!! Warning !!!

Anybody with access to the QF-Test daemon can start any program on the machine running the daemon with the rights of the user account that the daemon is running under, so access should be granted only to trusted users.

If you are not running the daemon in a secure environment where every user is trusted or if you are creating your own library to connect to the QF-Test daemon, you definitely should read section 53.3 about how to secure daemon communication with SSL.

To work with a daemon, you must first launch it on any computer in your network (of course, this host can also be localhost):

qftest -batch -daemon -daemonport 12345
Example 23.9:  Launching a QF-Test daemon

Note Important compatibility note:

3.5+ Starting with QF-Test version 3.5, SSL is used for daemon communication by default. To interact with a QF-Test version older than 3.5 you must start the daemon with an empty -keystore <keystore file> argument in the form:

qftest -batch -keystore= -daemon -daemonport 12345
Example 23.10:  Launching a QF-Test daemon without SSL

If you omit the argument -daemonport, the daemon will listen on QF-Test's standard port 3543. You may check whether the daemon is running by means of the netstat utility:

Windowsnetstat -a -p tcp -n | findstr "12345"

Linuxnetstat -a --tcp --numeric-ports | grep 12345

If you want to launch a daemon on a remote host, you may use for instance ssh or VNC. Your network administrator knows whether and how this works. To follow the examples below, a local daemon will be sufficient.

Controlling a daemon from QF-Test's command line

The easiest way to get in touch with a daemon is running QF-Test from the command line in the calldaemon mode. The following example checks if a daemon is listening at the specified host and port:

qftestc -batch -calldaemon -daemonhost localhost -daemonport 12345 -ping
Example 23.11:  Pinging a QF-Test daemon

In contrast to the netstat command from above -ping also works between different computers (if you check the daemon on your local computer, you can omit the argument -daemonhost).

What you actually want from a daemon is executing your test case(s) and getting back a run log file. It sounds and indeed looks quite similar to what you have seen before when running a test in batch mode:

qftest -batch -calldaemon -daemonhost somehost -daemonport 12345 -runlog c:\mylogs\+b -suitedir c:\mysuites suiteA.qft#"My test case"
Example 23.12:  Running a test case with the QF-Test daemon

Note In contrast to the batch mode, a 'Test case' or a 'Test set' node is always referenced here by its qualified name, for instance "My Test set.My Test case" (just to remember: -test <ID> may be used in batch mode). To execute the complete suite suiteA.qft, you can simply omit the test case or write suiteA.qft#..

If the daemon is running on a remote host, you have to specify it explicitly via -daemonhost (default is -daemonhost localhost). Note that the parameter -suitedir refers to the remote host (where the daemon is running) while -runlog defines a local file.

3.4+ In case you cannot easily observe the test running on a remote host, you may find it convenient to add the argument -verbose to get status output in the console (on Windows, use qftestc to see the output).

A running daemon, no matter whether local or remote, can be terminated with the calldaemon command -terminate:

qftest -batch -calldaemon -daemonport 12345 -daemonhost localhost -terminate
Example 23.13:  Terminating a QF-Test daemon

A complete list of the calldaemon parameters can be found in the chapter Command line arguments and exit codes.

Controlling a daemon with the daemon API

Using the QF-Test command line to control a daemon was quite easy. On the other hand, to get all capabilities of a daemon, you have to deal with the daemon API. In this section we will concentrate on some basic examples, the whole interface is described in chapter 53.

To get started with the daemon API, insert a 'Server script' node with the following code:

from de.qfs.apps.qftest.daemon import DaemonRunContext
from de.qfs.apps.qftest.daemon import DaemonLocator

host = "localhost"
port = 12345
# Leading r means raw string to allow normal backslashes in the path string.
testcase = r"c:\mysuites\suiteA.qft#My test case"
timeout = 60 * 1000

def calldaemon(host, port, testcase, timeout=0):
    daemon = DaemonLocator.instance().locateDaemon(host, port)
    trd = daemon.createTestRunDaemon()
    context = trd.createContext()
    if not context.waitForRunState(DaemonRunContext.STATE_FINISHED, timeout):
        # Run did not finish, terminate it
        if not context.waitForRunState(DaemonRunContext.STATE_FINISHED, 5000):
            # Context is deadlocked
            raise UserException("No reply from daemon RunContext.")
        rc.logError("Daemon call did not terminate and had to be stopped.")
    result = context.getResult()
    log = context.getRunLog()
    return result

result = calldaemon(host, port, testcase, timeout)
rc.logMessage("Result from daemon: %d" %result)
Example 23.14:  Daemon API in a 'Server script'

The script shows the basic mechanisms to control a daemon:

  • First find a running daemon with locateDaemon.
  • Provide an environment for test runs by calling createTestRunDaemon.
  • To run a test, you need a context object (createContext). The creation of that object requires a QF-Test run-time license.
  • Now the context enables you to start a test run (runTest) and to query about its current state. waitForRunState waits during the defined timeout (in milliseconds) until the specified state has occurred. In the example above, we wait for the test to terminate within one minute.
  • Finally, when the test run has terminated, the context can query the test result with the method getResult (cf. Exit codes for QF-Test).
  • Moreover, you can use the context to get the run log of the daemon test run. It can be included in the local run log by means of the rc method addDaemonLog.

Note To keep it small and simple, the example script does not contain any error handling. However, particularly when working with a daemon, you should check every method call.

Note Driving a daemon from a 'Server script' has the disadvantage of consuming an additional QF-Test license to run the script node interactively or in batch mode. However, this doesn't apply nor for the above-mentioned calldaemon mode neither for the case when controlling a daemon outside QF-Test (see below).

The usage of the daemon API is not restricted to 'Server scripts'. Outside QF-Test a daemon can be contacted by means of a Java program or, more easily, a Groovy script. The following Groovy script works with several running daemons and may serve as a starting point for load tests. Suppose we have started some daemons in our network, each on a separate machine. We want to execute a test case simultaneously by all of the daemons and we want to save a run log for every single test run (daemon1.qrl, ..., daemonN.qrl). The test suite containing the test case to be executed may be available for all daemon instances via the network drive z:).

import de.qfs.apps.qftest.daemon.DaemonLocator
import de.qfs.apps.qftest.daemon.DaemonRunContext

def testcase = "z:\\mysuites\\suiteA.qft#My test case"
def logfile = "c:\\mylogs\\daemon"
def timeout = 120 * 1000
def keystore = "z:\\mysuites\\mydaemon.keystore"
def password = "verySecret"

def locator = DaemonLocator.instance()
def daemons = locator.locateDaemons(10000)
def contexts = []
// Start tests
for (daemon in daemons) {
    def trd = daemon.createTestRunDaemon()
    trd.setGlobal('machines', daemons.size().toString())
    def context = trd.createContext()
    contexts << context
// Wait for tests to terminate
for (i in 0..<contexts.size()) {
    def context = contexts[i]
    context.waitForRunState(DaemonRunContext.STATE_FINISHED, timeout)
    byte[] runlog = context.getRunLog()
    def fos = new FileOutputStream("$logfile${i + 1}.qrl")
Example 23.15:  Groovy daemon script CallDaemon.groovy

To run that Groovy script, you need the QF-Test libraries qftest.jar, qfshared.jar, and qflib.jar as well as the Groovy library, which is also part of the QF-Test installation. The following batch script shows how it works:

@echo off
set qftestdir=c:\programs\qftest\qftest-7.1.5
set qflibdir=%qftestdir%\qflib
set classpath=%qftestdir%\lib\groovy-all.jar
set classpath=%classpath%;%qflibdir%\qftest.jar;%qflibdir%\qfshared.jar;%qflibdir%\qflib.jar
java -cp %classpath% groovy.ui.GroovyMain CallDaemon
Example 23.16:  Batch script calldaemon.bat to run Calldaemon.groovy

When accessed from externally, the DaemonLocator can only determine the default keystore to encrypt the daemon communication automatically, if the qftest.jar file is loaded from the QF-Test-directory (as shown in the batch script). Alternatively (as seen in the groovy script), the keystore can be specified explicetly by calling setKeystore and setKeystorePassword, or indirectly with the system properties javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword.

To make the daemon example a load test (cf. chapter 31), you have to synchronize the test runs inside of "My test case" (e. g. after starting the SUT). This can be done by means of the rc method syncThreads:

def machines = rc.getNum('machines')
rc.syncThreads('startup', 60000, -1, machines)
Example 23.17:  Groovy 'Server script' node to synchronize the test runs

The variable machines denotes the number of hosts with a daemon running on them. Best define it in the 'Test suite' node of the test suite with a default value of 1. When running the Groovy script, it will be overwritten with the correct value.