Skip to content
Browse files
QGIS testing environment docker backport
  • Loading branch information
elpaso committed Nov 30, 2018
1 parent ae76c05 commit cbc344611100a8be9fca5b3ec574076574103202
@@ -31,3 +31,7 @@ PyQgsAuthManagerPKIPostgresTest

# Needs an OpenCL device, the library is not enough

@@ -0,0 +1,258 @@
QGIS Docker images

The QGIS project provides a few official docker images that can be
used for testing purposes.

These dockers are currently used to run continuous integration
tests for the QGIS project itself and to run continuous integration
tests for several third party Python plugins.

The images are automatically built every day and pushed on docker hub
to the QGIS account:

# Available images

## Dependencies image


This is a simple base image that contains all the dependencies required to build
QGIS, it is used by the other images.

Multiple versions of this image may be available: the suffix in the image name indicates the Ubuntu version they are based on.

## Main QGIS image


This is the main image containing a build of QGIS.

The docker tags for this image are assigned for each point release (prefixed with `final-`), for the active development branches (prefixed with `release-`) while the `latest` tag refers to a build of the current master branch.

### Features

The docker file builds QGIS from the current directory and
sets up a testing environment suitable for running tests
inside QGIS.

You can use this docker image to test QGIS and/or to run unit tests inside
QGIS, `xvfb` (A fake X server) is available and running as a service inside
the container to allow for fully automated headless testing in CI pipelines
such as Travis or Circle-CI.

### Building

You can build the image from the main directory of the QGIS source tree with:

$ docker build -t qgis/qgis:latest \
--build-arg DOCKER_TAG=latest \
-f .docker/qgis.dockerfile \

The `DOCKER_TAG` argument, can be used to specify the tag of the dependencies image.

### Running QGIS

You can also use this image to run QGIS on your desktop.

To run a QGIS container, assuming that you want to use your current
display to use QGIS and the image is tagged `qgis/qgis:latest` you can use a script like the one here below:

# Allow connections from any host
$ xhost +
$ docker run --rm -it --name qgis \
-v /tmp/.X11-unix:/tmp/.X11-unix \
qgis/qgis:latest qgis

This code snippet will launch QGIS inside a container and display the
application on your screen.

### Running unit tests inside QGIS

Suppose that you have local directory containing the tests you want to execute into QGIS:


To run the tests inside the container, you must mount the directory that
contains the tests (e.g. your local directory `/my_tests`) into a volume
that is accessible by the container, see `-v /my_tests/:/tests_directory`
in the example below:

$ docker run -d --name qgis -v /tmp/.X11-unix:/tmp/.X11-unix \
-v /my_tests/:/tests_directory \
-e DISPLAY=:99 \

Here is an extract of ``:

# -*- coding: utf-8 -*-
import sys
from qgis.testing import unittest
class TestTest(unittest.TestCase):
def test_passes(self):
def run_all():
"""Default function that is called by the runner if nothing else is specified"""
suite = unittest.TestSuite()
suite.addTests(unittest.makeSuite(TestTest, 'test'))
unittest.TextTestRunner(verbosity=3, stream=sys.stdout).run(suite)

When done, you can invoke the test runnner by specifying the test
that you want to run, for instance:

$ docker exec -it qgis sh -c "cd /tests_directory && travis_tests.test_TravisTest.run_fail"

The test can be specified by using a dotted notation, similar to Python
import notation, by default the function named `run_all` will be executed
but you can pass another function name as the last item in the dotted syntax:

# Call the default function "run_all" inside test_TravisTest module travis_tests.test_TravisTest
# Call the function "run_fail" inside test_TravisTest module travis_tests.test_TravisTest.run_fail

Please note that in order to make the test script accessible to Python
the calling command must ensure that the tests are in Python path.
Common patterns are:
- change directory to the one containing the tests (like in the examples above)
- add to `PYTHONPATH` the directory containing the tests

#### Running tests for a Python plugin

All the above considerations applies to this case too, however in order
to simulate the installation of the plugin inside QGIS, you'll need to
make an additional step: call ` <YourPluginName>` in the
docker container before actually running the tests (see the paragraph
about Running on Travis for a complete example).

The `` script prepares QGIS to run in headless mode and
simulate the plugin installation process:

- creates the QGIS profile folders
- "installs" the plugin by making a symbolic link from the profiles folder to the plugin folder
- installs `` monkey patches to prevent blocking dialogs
- enables the plugin

Please note that depending on your plugin repository internal directory structure
you may need to adjust (remove and create) the symbolic link created by ``,
this is required in particular if the real plugin code in your repository is contained
in the main directory and not in a subdirectory with the same name of the plugin
internal name (the name in `metadata.txt`).

#### Options for the test runner

The env var `QGIS_EXTRA_OPTIONS` defaults to an empty string and can
contains extra parameters that are passed to QGIS by the test runner.

#### Running on Travis

Here is a simple example for running unit tests of a small QGIS plugin (named *QuickWKT*), assuming that the tests are in `tests/` under
the main directory of the QuickWKT plugin:

- docker
- docker run -d --name qgis-testing-environment -v ${TRAVIS_BUILD_DIR}:/tests_directory -e DISPLAY=:99 qgis/qgis:latest
- sleep 10 # This is required to allow xvfb to start
# Setup qgis and enables the plugin
- docker exec -it qgis-testing-environment sh -c " QuickWKT"
# Additional steps (for example make or paver setup) here
# Fix the symlink created by
- docker exec -it qgis-testing-environment sh -c "rm -f /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/QuickWKT"
- docker exec -it qgis-testing-environment sh -c "ln -s /tests_directory/ /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/QuickWKT"
- docker exec -it qgis-testing-environment sh -c "cd /tests_directory && tests.test_Plugin"

Please note that `cd /tests_directory && ` before the call to `` could be avoided here, because QGIS automatically
adds the plugin main directory to Python path.

#### Running on Circle-CI

Here is an example for running unit tests of a small QGIS plugin (named *QuickWKT*), assuming
that the tests are in `tests/` under the main directory of the QuickWKT plugin:

version: 2
- image: qgis/qgis:latest
DISPLAY: ":99"
working_directory: /tests_directory
- checkout
- run:
name: Setup plugin
command: | QuickWKT
- run:
name: Fix installation path created by qgis_setup.s
command: |
rm -f /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/QuickWKT
ln -s /tests_directory/ /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/qgisce
- run:
name: run tests
command: |
sh -c "/usr/bin/Xvfb :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset -nolisten tcp &" tests.test_Plugin

#### Implementation notes

The main goal of the test runner in this image is to execute unit tests
inside a real instance of QGIS (not a mocked one).

The QGIS tests should be runnable from a Travis/Circle-CI CI job.

The implementation is:

- run the docker, mounting as volumes the unit tests folder in `/tests_directory`
(or the QGIS plugin folder if the unit tests belong to a plugin and the
plugin is needed to run the tests)
- execute ` MyPluginName` script in docker that sets up QGIS to
avoid blocking modal dialogs and installs the plugin into QGIS if needed
- create config and python plugin folders for QGIS
- enable the plugin in the QGIS configuration file
- install the `` script to disable python exception modal dialogs
- execute the tests by running ` MyPluginName.tests.tests_MyTestModule.run_my_tests_function`
- the output of the tests is captured by the `` script and
searched for `FAILED` (that is in the standard unit tests output) and other
string that indicate a failure or success condition, if a failure condition
is identified, the script exits with `1` otherwise it exits with `0`.

`` accepts a dotted notation path to the test module that
can end with the function that has to be called inside the module to run the
tests. The last part (`.run_my_tests_function`) can be omitted and defaults to
@@ -8,6 +8,7 @@ FROM qgis/qgis3-build-deps:${DOCKER_TAG}
MAINTAINER Denis Rouzaud <>

LABEL Description="Docker container with QGIS" Vendor="" Version="1.1"

ENV CC=/usr/lib/ccache/clang
ENV CXX=/usr/lib/ccache/clang++
@@ -26,7 +27,7 @@ RUN cmake \
@@ -46,4 +47,28 @@ RUN cmake \
&& ninja install \
&& rm -rf /usr/src/QGIS

# Python testing environment setup

# Add QGIS test runner
COPY .docker/qgis_resources/test_runner/qgis_* /usr/bin/

# Make all scripts executable
RUN chmod +x /usr/bin/qgis_*

# Add supervisor service configuration script
COPY .docker/qgis_resources/supervisor/supervisord.conf /etc/supervisor/
COPY .docker/qgis_resources/supervisor/supervisor.xvfb.conf /etc/supervisor/supervisor.d/

# Python paths are for
# - kartoza images (compiled)
# - deb installed
# - built from git
# needed to find PyQt wrapper provided by QGIS
ENV PYTHONPATH=/usr/share/qgis/python/:/usr/lib/python3/dist-packages/qgis:/usr/share/qgis/python/qgis


# Run supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
@@ -1,7 +1,7 @@
FROM ubuntu:18.04
MAINTAINER Denis Rouzaud <>

LABEL Description="Docker container with QGIS dependencies" Vendor="" Version="1.0"
LABEL Description="Docker container with QGIS dependencies" Vendor="" Version="1.1"

# && echo "deb xenial main" >> /etc/apt/sources.list \
# && echo "deb-src xenial main" >> /etc/apt/sources.list \
@@ -11,7 +11,8 @@ LABEL Description="Docker container with QGIS dependencies" Vendor="" Ve
RUN apt-get update \
&& apt-get install -y software-properties-common \
&& apt-get update \
&& apt-get install -y \
&& DEBIAN_FRONTEND=noninteractive \
apt-get install -y \
apt-transport-https \
bison \
ca-certificates \
@@ -25,6 +26,7 @@ RUN apt-get update \
git \
graphviz \
grass-dev \
libexiv2-dev \
libexpat1-dev \
libfcgi-dev \
libgdal-dev \
@@ -76,6 +78,7 @@ RUN apt-get update \
python3-pyqt5.qsci \
python3-pyqt5.qtsql \
python3-pyqt5.qtsvg \
python3-pyqt5.qtwebkit \
python3-sip \
python3-sip-dev \
python3-termcolor \
@@ -102,6 +105,11 @@ RUN apt-get update \
xfonts-base \
xfonts-scalable \
xvfb \
opencl-headers \
ocl-icd-libopencl1 \
ocl-icd-opencl-dev \
supervisor \
expect \
&& pip3 install \
psycopg2 \
numpy \
@@ -113,6 +121,12 @@ RUN apt-get update \
owslib \
oauthlib \
pyopenssl \
pep8 \
pexpect \
capturer \
sphinx \
requests \
six \
&& apt-get clean

@@ -0,0 +1,7 @@
@@ -0,0 +1,4 @@
; Supervisor config file for Xvfb

command=/usr/bin/Xvfb :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset -nolisten tcp
@@ -0,0 +1,13 @@
; Supervisor config file.


files = /etc/supervisor/supervisor.d/*.conf

0 comments on commit cbc3446

Please sign in to comment.