Browse Source

Merge pull request #1572 from kmarilleau/travis_to_github_actions

Travis to GitHub actions
Nicolas Duchon 4 years ago
parent
commit
aa9386129d

+ 37 - 0
.github/workflows/test.yml

@@ -0,0 +1,37 @@
+name: Test
+
+on: [push, pull_request]
+
+jobs:
+  unit:
+    name: Unit Test
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: true
+      matrix:
+        base_docker_image: [alpine, debian]
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Set up Python 3.9
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.9
+
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install -r python-requirements.txt
+        working-directory: test/requirements
+
+      - name: Build Docker web server image
+        run: make build-webserver
+
+      - name: Build Docker nginx proxy test image
+        run: make build-nginx-proxy-test-${{ matrix.base_docker_image }}
+
+      - name: Run tests
+        run: pytest
+        working-directory: test

+ 9 - 5
Makefile

@@ -2,15 +2,19 @@
 .PHONY : test-debian test-alpine test
 
 
-update-dependencies:
-	test/requirements/build.sh
+build-webserver:
+	docker build -t web test/requirements/web
 
-test-debian: update-dependencies
+build-nginx-proxy-test-debian:
 	docker build -t nginxproxy/nginx-proxy:test .
-	test/pytest.sh
 
-test-alpine: update-dependencies
+build-nginx-proxy-test-alpine:
 	docker build -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .
+
+test-debian: build-webserver build-nginx-proxy-test-debian
+	test/pytest.sh
+
+test-alpine: build-webserver build-nginx-proxy-test-alpine
 	test/pytest.sh
 
 test: test-debian test-alpine

+ 6 - 13
README.md

@@ -415,22 +415,15 @@ Before submitting pull requests or issues, please check github to make sure an e
 
 #### Running Tests Locally
 
-To run tests, you need to prepare the docker image to test which must be tagged `nginxproxy/nginx-proxy:test`:
-
-    docker build -t nginxproxy/nginx-proxy:test .  # build the Debian variant image
-
-and call the [test/pytest.sh](test/pytest.sh) script.
-
-Then build the Alpine variant of the image:
-
-    docker build -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .  # build the Alpline variant image
-
-and call the [test/pytest.sh](test/pytest.sh) script again.
+To run tests, you just need to run the command below:
 
+    make test
 
-If your system has the `make` command, you can automate those tasks by calling:
+This commands run tests on two variants of the nginx-proxy docker image: Debian and Alpine.
 
-    make test
+You can run the tests for each of these images with their respective commands:
 
+    make test-debian
+    make test-alpine
 
 You can learn more about how the test suite works and how to write new tests in the [test/README.md](test/README.md) file.

+ 5 - 10
test/README.md

@@ -4,9 +4,8 @@ Nginx proxy test suite
 Install requirements
 --------------------
 
-You need [python 2.7](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/installing/) installed. Then run the commands:
+You need [python 3.9](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/installing/) installed. Then run the commands:
 
-    requirements/build.sh
     pip install -r requirements/python-requirements.txt
 
 If you can't install those requirements on your computer, you can alternatively use the _pytest.sh_ script which will run the tests from a Docker container which has those requirements.
@@ -15,14 +14,11 @@ If you can't install those requirements on your computer, you can alternatively
 Prepare the nginx-proxy test image
 ----------------------------------
 
-    docker build -t nginxproxy/nginx-proxy:test ..
+    make build-nginx-proxy-test-debian
 
 or if you want to test the alpine flavor:
 
-    docker build -t nginxproxy/nginx-proxy:test -f Dockerfile.alpine ..
-
-make sure to tag that test image exactly `nginxproxy/nginx-proxy:test` or the test suite won't work.
-
+    make build-nginx-proxy-test-alpine
 
 Run the test suite
 ------------------
@@ -61,7 +57,7 @@ The fixture will run the _docker-compose_ command with the `-f` option to load t
 
 In the case you are running pytest from within a docker container, the `docker_compose` fixture will make sure the container running pytest is attached to all docker networks. That way, your test will be able to reach any of them.
 
-In your tests, you can use the `docker_compose` variable to query and command the docker daemon as it provides you with a [client from the docker python module](https://docker-py.readthedocs.io/en/2.0.2/client.html#client-reference).
+In your tests, you can use the `docker_compose` variable to query and command the docker daemon as it provides you with a [client from the docker python module](https://docker-py.readthedocs.io/en/4.4.4/client.html#client-reference).
 
 Also this fixture alters the way the python interpreter resolves domain names to IP addresses in the following ways:
 
@@ -99,8 +95,7 @@ Furthermore, the nginxproxy methods accept an additional keyword parameter: `ipv
 
 ### The web docker image
 
-When you ran the `requirements/build.sh` script earlier, you built a [`web`](requirements/README.md) docker image which is convenient for running a small web server in a container. This image can produce containers that listens on multiple ports at the same time.
-
+When you run the `make build-webserver` command, you built a [`web`](requirements/README.md) docker image which is convenient for running a small web server in a container. This image can produce containers that listens on multiple ports at the same time.
 
 ### Testing TLS
 

+ 44 - 42
test/conftest.py

@@ -1,4 +1,3 @@
-from __future__ import print_function
 import contextlib
 import logging
 import os
@@ -133,7 +132,7 @@ def container_ip(container):
             pytest.skip("This system does not support IPv6")
         ip = container_ipv6(container)
         if ip == '':
-            pytest.skip("Container %s has no IPv6 address" % container.name)
+            pytest.skip(f"Container {container.name} has no IPv6 address")
         else:
             return ip
     else:
@@ -142,7 +141,7 @@ def container_ip(container):
             return net_info["bridge"]["IPAddress"]
 
         # not default bridge network, fallback on first network defined
-        network_name = net_info.keys()[0]
+        network_name = list(net_info.keys())[0]
         return net_info[network_name]["IPAddress"]
 
 
@@ -155,7 +154,7 @@ def container_ipv6(container):
         return net_info["bridge"]["GlobalIPv6Address"]
 
     # not default bridge network, fallback on first network defined
-    network_name = net_info.keys()[0]
+    network_name = list(net_info.keys())[0]
     return net_info[network_name]["GlobalIPv6Address"]
 
 
@@ -167,15 +166,15 @@ def nginx_proxy_dns_resolver(domain_name):
     :return: IP or None
     """
     log = logging.getLogger('DNS')
-    log.debug("nginx_proxy_dns_resolver(%r)" % domain_name)
+    log.debug(f"nginx_proxy_dns_resolver({domain_name!r})")
     if 'nginx-proxy' in domain_name:
         nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
         if len(nginxproxy_containers) == 0:
-            log.warn("no container found from image nginxproxy/nginx-proxy:test while resolving %r", domain_name)
+            log.warn(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
             return
         nginxproxy_container = nginxproxy_containers[0]
         ip = container_ip(nginxproxy_container)
-        log.info("resolving domain name %r as IP address %s of nginx-proxy container %s" % (domain_name, ip, nginxproxy_container.name))
+        log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}")
         return ip
 
 def docker_container_dns_resolver(domain_name):
@@ -186,24 +185,24 @@ def docker_container_dns_resolver(domain_name):
     :return: IP or None
     """
     log = logging.getLogger('DNS')
-    log.debug("docker_container_dns_resolver(%r)" % domain_name)
+    log.debug(f"docker_container_dns_resolver({domain_name!r})")
 
-    match = re.search('(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name)
+    match = re.search(r'(^|.+\.)(?P<container>[^.]+)\.container\.docker$', domain_name)
     if not match:
-        log.debug("%r does not match" % domain_name)
+        log.debug(f"{domain_name!r} does not match")
         return
 
     container_name = match.group('container')
-    log.debug("looking for container %r" % container_name)
+    log.debug(f"looking for container {container_name!r}")
     try:
         container = docker_client.containers.get(container_name)
     except docker.errors.NotFound:
-        log.warn("container named %r not found while resolving %r" % (container_name, domain_name))
+        log.warn(f"container named {container_name!r} not found while resolving {domain_name!r}")
         return
-    log.debug("container %r found (%s)" % (container.name, container.short_id))
+    log.debug(f"container {container.name!r} found ({container.short_id})")
 
     ip = container_ip(container)
-    log.info("resolving domain name %r as IP address %s of container %s" % (domain_name, ip, container.name))
+    log.info(f"resolving domain name {domain_name!r} as IP address {ip} of container {container.name}")
     return ip 
 
 
@@ -216,7 +215,7 @@ def monkey_patch_urllib_dns_resolver():
     prv_getaddrinfo = socket.getaddrinfo
     dns_cache = {}
     def new_getaddrinfo(*args):
-        logging.getLogger('DNS').debug("resolving domain name %s" % repr(args))
+        logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}")
         _args = list(args)
 
         # custom DNS resolvers
@@ -244,7 +243,7 @@ def remove_all_containers():
     for container in docker_client.containers.list(all=True):
         if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER and container.id.startswith(socket.gethostname()):
             continue  # pytest is running within a Docker container, so we do not want to remove that particular container
-        logging.info("removing container %s" % container.name)
+        logging.info(f"removing container {container.name}")
         container.remove(v=True, force=True)
 
 
@@ -253,27 +252,30 @@ def get_nginx_conf_from_container(container):
     return the nginx /etc/nginx/conf.d/default.conf file content from a container
     """
     import tarfile
-    from cStringIO import StringIO
-    strm, stat = container.get_archive('/etc/nginx/conf.d/default.conf')
-    with tarfile.open(fileobj=StringIO(strm.read())) as tf:
+    from io import BytesIO
+
+    strm_generator, stat = container.get_archive('/etc/nginx/conf.d/default.conf')
+    strm_fileobj = BytesIO(b"".join(strm_generator))
+
+    with tarfile.open(fileobj=strm_fileobj) as tf:
         conffile = tf.extractfile('default.conf')
         return conffile.read()
 
 
 def docker_compose_up(compose_file='docker-compose.yml'):
-    logging.info('docker-compose -f %s up -d' % compose_file)
+    logging.info(f'docker-compose -f {compose_file} up -d')
     try:
-        subprocess.check_output(shlex.split('docker-compose -f %s up -d' % compose_file), stderr=subprocess.STDOUT)
-    except subprocess.CalledProcessError, e:
-        pytest.fail("Error while runninng 'docker-compose -f %s up -d':\n%s" % (compose_file, e.output), pytrace=False)
+        subprocess.check_output(shlex.split(f'docker-compose -f {compose_file} up -d'), stderr=subprocess.STDOUT)
+    except subprocess.CalledProcessError as e:
+        pytest.fail(f"Error while runninng 'docker-compose -f {compose_file} up -d':\n{e.output}", pytrace=False)
 
 
 def docker_compose_down(compose_file='docker-compose.yml'):
-    logging.info('docker-compose -f %s down' % compose_file)
+    logging.info(f'docker-compose -f {compose_file} down')
     try:
-        subprocess.check_output(shlex.split('docker-compose -f %s down' % compose_file), stderr=subprocess.STDOUT)
-    except subprocess.CalledProcessError, e:
-        pytest.fail("Error while runninng 'docker-compose -f %s down':\n%s" % (compose_file, e.output), pytrace=False)
+        subprocess.check_output(shlex.split(f'docker-compose -f {compose_file} down'), stderr=subprocess.STDOUT)
+    except subprocess.CalledProcessError as e:
+        pytest.fail(f"Error while runninng 'docker-compose -f {compose_file} down':\n{e.output}", pytrace=False)
 
 
 def wait_for_nginxproxy_to_be_ready():
@@ -286,7 +288,7 @@ def wait_for_nginxproxy_to_be_ready():
         return
     container = containers[0]
     for line in container.logs(stream=True):
-        if "Watching docker events" in line:
+        if b"Watching docker events" in line:
             logging.debug("nginx-proxy ready")
             break
 
@@ -307,7 +309,7 @@ def find_docker_compose_file(request):
     if docker_compose_file_module_variable is not None:
         docker_compose_file = os.path.join( test_module_dir, docker_compose_file_module_variable)
         if not os.path.isfile(docker_compose_file):
-            raise ValueError("docker compose file %r could not be found. Check your test module `docker_compose_file` variable value." % docker_compose_file)
+            raise ValueError(f"docker compose file {docker_compose_file!r} could not be found. Check your test module `docker_compose_file` variable value.")
     else:
         if os.path.isfile(yml_file):
             docker_compose_file = yml_file
@@ -319,7 +321,7 @@ def find_docker_compose_file(request):
     if not os.path.isfile(docker_compose_file):
         logging.error("Could not find any docker-compose file named either '{0}.yml', '{0}.yaml' or 'docker-compose.yml'".format(request.module.__name__))
 
-    logging.debug("using docker compose file %s" % docker_compose_file)
+    logging.debug(f"using docker compose file {docker_compose_file}")
     return docker_compose_file
 
 
@@ -333,15 +335,15 @@ def connect_to_network(network):
         try:
             my_container = docker_client.containers.get(socket.gethostname())
         except docker.errors.NotFound:
-            logging.warn("container %r not found" % socket.gethostname())
+            logging.warn(f"container {socket.gethostname()!r} not found")
             return
 
         # figure out our container networks
-        my_networks = my_container.attrs["NetworkSettings"]["Networks"].keys()
+        my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
 
         # make sure our container is connected to the nginx-proxy's network
         if network not in my_networks:
-            logging.info("Connecting to docker network: %s" % network.name)
+            logging.info(f"Connecting to docker network: {network.name}")
             network.connect(my_container)
             return network
 
@@ -356,15 +358,15 @@ def disconnect_from_network(network=None):
         try:
             my_container = docker_client.containers.get(socket.gethostname())
         except docker.errors.NotFound:
-            logging.warn("container %r not found" % socket.gethostname())
+            logging.warn(f"container {socket.gethostname()!r} not found")
             return
 
         # figure out our container networks
-        my_networks_names = my_container.attrs["NetworkSettings"]["Networks"].keys()
+        my_networks_names = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
 
         # disconnect our container from the given network
         if network.name in my_networks_names:
-            logging.info("Disconnecting from network %s" % network.name)
+            logging.info(f"Disconnecting from network {network.name}")
             network.disconnect(my_container)
 
 
@@ -378,7 +380,7 @@ def connect_to_all_networks():
         return []
     else:
         # find the list of docker networks
-        networks = filter(lambda network: len(network.containers) > 0 and network.name != 'bridge', docker_client.networks.list())
+        networks = [network for network in docker_client.networks.list() if len(network.containers) > 0 and network.name != 'bridge']
         return [connect_to_network(network) for network in networks]
 
 
@@ -388,7 +390,7 @@ def connect_to_all_networks():
 # 
 ###############################################################################
 
-@pytest.yield_fixture(scope="module")
+@pytest.fixture(scope="module")
 def docker_compose(request):
     """
     pytest fixture providing containers described in a docker compose file. After the tests, remove the created containers
@@ -412,7 +414,7 @@ def docker_compose(request):
     restore_urllib_dns_resolver(original_dns_resolver)
 
 
-@pytest.yield_fixture()
+@pytest.fixture()
 def nginxproxy():
     """
     Provides the `nginxproxy` object that can be used in the same way the requests module is:
@@ -456,7 +458,7 @@ def pytest_runtest_makereport(item, call):
 def pytest_runtest_setup(item):
     previousfailed = getattr(item.parent, "_previousfailed", None)
     if previousfailed is not None:
-        pytest.xfail("previous test failed (%s)" % previousfailed.name)
+        pytest.xfail(f"previous test failed ({previousfailed.name})")
 
 ###############################################################################
 # 
@@ -469,5 +471,5 @@ try:
 except docker.errors.ImageNotFound:
     pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing")
 
-if docker.__version__ != "2.1.0":
-    pytest.exit("This test suite is meant to work with the python docker module v2.1.0")
+if docker.__version__ != "4.4.4":
+    pytest.exit("This test suite is meant to work with the python docker module v4.4.4")

+ 3 - 1
test/pytest.ini

@@ -1,3 +1,5 @@
 [pytest]
 # disable the creation of the `.cache` folders
-addopts = -p no:cacheprovider --ignore=requirements --ignore=certs -r s -v
+addopts = -p no:cacheprovider --ignore=requirements --ignore=certs -r s -v
+markers =
+    incremental: mark a test as incremental.

+ 1 - 4
test/requirements/Dockerfile-nginx-proxy-tester

@@ -1,7 +1,4 @@
-FROM python:2.7-alpine
-
-# Note: we're using alpine because it has openssl 1.0.2, which we need for testing
-RUN apk add --update bash openssl curl && rm -rf /var/cache/apk/*
+FROM python:3.9
 
 COPY python-requirements.txt /requirements.txt
 RUN pip install -r /requirements.txt

+ 1 - 1
test/requirements/README.md

@@ -2,7 +2,7 @@ This directory contains resources to build Docker images tests depend on
 
 # Build images
 
-    ./build.sh   
+    make build-webserver
 
 
 # python-requirements.txt

+ 0 - 6
test/requirements/build.sh

@@ -1,6 +0,0 @@
-#!/bin/bash
-set -e
-
-DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-
-docker build -t web $DIR/web

+ 5 - 5
test/requirements/python-requirements.txt

@@ -1,5 +1,5 @@
-backoff==1.3.2
-docker-compose==1.11.2
-docker==2.1.0
-pytest==3.0.5
-requests==2.11.1
+backoff==1.10.0
+docker-compose==1.28.5
+docker==4.4.4
+pytest==6.2.2
+requests==2.25.1

+ 3 - 3
test/requirements/web/webserver.py

@@ -13,13 +13,13 @@ class Handler(http.server.SimpleHTTPRequestHandler):
         if self.path == "/headers":
             response_body += self.headers.as_string()
         elif self.path == "/port":
-            response_body += "answer from port %s\n" % PORT
+            response_body += f"answer from port {PORT}\n"
         elif re.match("/status/(\d+)", self.path):
             result = re.match("/status/(\d+)", self.path)
             response_code = int(result.group(1))
-            response_body += "answer with response code %s\n" % response_code
+            response_body += f"answer with response code {response_code}\n"
         elif self.path == "/":
-            response_body += "I'm %s\n" % os.environ['HOSTNAME']
+            response_body += f"I'm {os.environ['HOSTNAME']}\n"
         else:
             response_body += "No route for this path!\n"
             response_code = 404

+ 2 - 2
test/stress_tests/test_deleted_cert/test_restart_while_missing_cert.py

@@ -12,7 +12,7 @@ script_dir = os.path.dirname(__file__)
 pytestmark = pytest.mark.xfail()  # TODO delete this marker once those issues are fixed
 
 
-@pytest.yield_fixture(scope="module", autouse=True)
+@pytest.fixture(scope="module", autouse=True)
 def certs():
     """
     pytest fixture that provides cert and key files into the tmp_certs directory
@@ -43,7 +43,7 @@ def test_http_web_is_301(docker_compose, nginxproxy):
 def test_https_web_is_200(docker_compose, nginxproxy):
     r = nginxproxy.get("https://web.nginx-proxy/port")
     assert r.status_code == 200
-    assert 'answer from port 81\n' in r.text
+    assert "answer from port 81\n" in r.text
 
 
 @pytest.mark.incremental

+ 1 - 1
test/test_custom/test_location-per-vhost.py

@@ -19,4 +19,4 @@ def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
     assert "X-test" not in r.headers
 
 def test_custom_block_is_present_in_nginx_generated_conf(docker_compose, nginxproxy):
-    assert "include /etc/nginx/vhost.d/web1.nginx-proxy.local_location;" in nginxproxy.get_conf()
+    assert b"include /etc/nginx/vhost.d/web1.nginx-proxy.local_location;" in nginxproxy.get_conf()

+ 1 - 1
test/test_default-host.yml

@@ -10,7 +10,7 @@ web1:
 
 # WHEN nginx-proxy runs with DEFAULT_HOST set to web1.tld
 sut:
-  image: jwilder/nginx-proxy:test
+  image: nginxproxy/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
     - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 14 - 10
test/test_dockergen/test_dockergen_v2.py

@@ -4,7 +4,7 @@ import logging
 import pytest
 
 
-@pytest.yield_fixture(scope="module")
+@pytest.fixture(scope="module")
 def nginx_tmpl():
     """
     pytest fixture which extracts the the nginx config template from
@@ -13,14 +13,18 @@ def nginx_tmpl():
     script_dir = os.path.dirname(__file__)
     logging.info("extracting nginx.tmpl from nginxproxy/nginx-proxy:test")
     docker_client = docker.from_env()
-    print(docker_client.containers.run(
-        image='nginxproxy/nginx-proxy:test',
-        remove=True,
-        volumes=['{current_dir}:{current_dir}'.format(current_dir=script_dir)],
-        entrypoint='sh',
-        command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format(
-            current_dir=script_dir),
-        stderr=True))
+    print(
+        docker_client.containers.run(
+            image="nginxproxy/nginx-proxy:test",
+            remove=True,
+            volumes=["{current_dir}:{current_dir}".format(current_dir=script_dir)],
+            entrypoint="sh",
+            command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format(
+                current_dir=script_dir
+            ),
+            stderr=True,
+        )
+    )
     yield
     logging.info("removing nginx.tmpl")
     os.remove(os.path.join(script_dir, "nginx.tmpl"))
@@ -35,4 +39,4 @@ def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy):
     r = nginxproxy.get("http://whoami.nginx.container.docker/")
     assert r.status_code == 200
     whoami_container = docker_compose.containers.get("whoami")
-    assert r.text == "I'm %s\n" % whoami_container.id[:12]
+    assert r.text == f"I'm {whoami_container.id[:12]}\n"

+ 20 - 30
test/test_dockergen/test_dockergen_v3.py

@@ -3,31 +3,17 @@ import docker
 import logging
 import pytest
 import re
-
-def versiontuple(v):
-    """
-    >>> versiontuple("1.12.3")
-    (1, 12, 3)
-
-    >>> versiontuple("1.13.0")
-    (1, 13, 0)
-
-    >>> versiontuple("17.03.0-ce")
-    (17, 3, 0)
-
-    >>> versiontuple("17.03.0-ce") < (1, 13)
-    False
-    """
-    return tuple(map(int, (v.split('-')[0].split("."))))
+from distutils.version import LooseVersion
 
 
-raw_version = docker.from_env().version()['Version']
+raw_version = docker.from_env().version()["Version"]
 pytestmark = pytest.mark.skipif(
-    versiontuple(raw_version) < (1, 13),
-    reason="Docker compose syntax v3 requires docker engine v1.13 or later (got %s)" % raw_version)
+    LooseVersion(raw_version) < LooseVersion("1.13"),
+    reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
+)
 
 
-@pytest.yield_fixture(scope="module")
+@pytest.fixture(scope="module")
 def nginx_tmpl():
     """
     pytest fixture which extracts the the nginx config template from
@@ -36,14 +22,18 @@ def nginx_tmpl():
     script_dir = os.path.dirname(__file__)
     logging.info("extracting nginx.tmpl from nginxproxy/nginx-proxy:test")
     docker_client = docker.from_env()
-    print(docker_client.containers.run(
-        image='nginxproxy/nginx-proxy:test',
-        remove=True,
-        volumes=['{current_dir}:{current_dir}'.format(current_dir=script_dir)],
-        entrypoint='sh',
-        command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format(
-            current_dir=script_dir),
-        stderr=True))
+    print(
+        docker_client.containers.run(
+            image="nginxproxy/nginx-proxy:test",
+            remove=True,
+            volumes=["{current_dir}:{current_dir}".format(current_dir=script_dir)],
+            entrypoint="sh",
+            command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format(
+                current_dir=script_dir
+            ),
+            stderr=True,
+        )
+    )
     yield
     logging.info("removing nginx.tmpl")
     os.remove(os.path.join(script_dir, "nginx.tmpl"))
@@ -58,9 +48,9 @@ def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy):
     r = nginxproxy.get("http://whoami.nginx.container.docker/")
     assert r.status_code == 200
     whoami_container = docker_compose.containers.get("whoami")
-    assert r.text == "I'm %s\n" % whoami_container.id[:12]
+    assert r.text == f"I'm {whoami_container.id[:12]}\n"
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import doctest
     doctest.testmod()

+ 1 - 1
test/test_events.py

@@ -7,7 +7,7 @@ import pytest
 from docker.errors import NotFound
 
 
-@pytest.yield_fixture()
+@pytest.fixture()
 def web1(docker_compose):
     """
     pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81.

+ 9 - 9
test/test_ssl/test_dhparam.py

@@ -26,7 +26,7 @@ def assert_log_contains(expected_log_line):
     """
     sut_container = docker_client.containers.get("nginxproxy")
     docker_logs = sut_container.logs(stdout=True, stderr=True, stream=False, follow=False)
-    assert expected_log_line in docker_logs
+    assert bytes(expected_log_line, encoding="utf8") in docker_logs
 
 
 def require_openssl(required_version):
@@ -42,7 +42,7 @@ def require_openssl(required_version):
     """
 
     def versiontuple(v):
-        clean_v = re.sub("[^\d\.]", "", v)
+        clean_v = re.sub(r"[^\d\.]", "", v)
         return tuple(map(int, (clean_v.split("."))))
 
     try:
@@ -52,10 +52,10 @@ def require_openssl(required_version):
     else:
         if not command_output:
             raise Exception("Could not get openssl version")
-        openssl_version = command_output.split()[1]
+        openssl_version = str(command_output.split()[1])
         return pytest.mark.skipif(
             versiontuple(openssl_version) < versiontuple(required_version),
-            reason="openssl v%s is less than required version %s" % (openssl_version, required_version))
+            reason=f"openssl v{openssl_version} is less than required version {required_version}")
 
 
 ###############################################################################
@@ -71,8 +71,8 @@ def test_dhparam_is_not_generated_if_present(docker_compose):
     assert_log_contains("Custom dhparam.pem file found, generation skipped")
 
     # Make sure the dhparam in use is not the default, pre-generated one
-    default_checksum = sut_container.exec_run("md5sum /app/dhparam.pem.default").split()
-    current_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").split()
+    default_checksum = sut_container.exec_run("md5sum /app/dhparam.pem.default").output.split()
+    current_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").output.split()
     assert default_checksum[0] != current_checksum[0]
 
 
@@ -87,7 +87,7 @@ def test_web5_dhparam_is_used(docker_compose):
     sut_container = docker_client.containers.get("nginxproxy")
     assert sut_container.status == "running"
 
-    host = "%s:443" % sut_container.attrs["NetworkSettings"]["IPAddress"]
+    host = f"{sut_container.attrs['NetworkSettings']['IPAddress']}:443"
     r = subprocess.check_output(
-        "echo '' | openssl s_client -connect %s -cipher 'EDH' | grep 'Server Temp Key'" % host, shell=True)
-    assert "Server Temp Key: X25519, 253 bits\n" == r
+        f"echo '' | openssl s_client -connect {host} -cipher 'EDH' | grep 'Server Temp Key'", shell=True)
+    assert b"Server Temp Key: X25519, 253 bits\n" == r

+ 4 - 4
test/test_ssl/test_dhparam_generation.py

@@ -22,7 +22,7 @@ def assert_log_contains(expected_log_line):
     """
     sut_container = docker_client.containers.get("nginxproxy")
     docker_logs = sut_container.logs(stdout=True, stderr=True, stream=False, follow=False)
-    assert expected_log_line in docker_logs
+    assert bytes(expected_log_line, encoding="utf8") in docker_logs
 
 
 ###############################################################################
@@ -35,10 +35,10 @@ def test_dhparam_is_generated_if_missing(docker_compose):
     sut_container = docker_client.containers.get("nginxproxy")
     assert sut_container.status == "running"
 
-    assert_log_contains("Generating DH parameters")
+    assert_log_contains("Generating DSA parameters")
     assert_log_contains("dhparam generation complete, reloading nginx")
 
     # Make sure the dhparam in use is not the default, pre-generated one
-    default_checksum = sut_container.exec_run("md5sum /app/dhparam.pem.default").split()
-    generated_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").split()
+    default_checksum = sut_container.exec_run("md5sum /app/dhparam.pem.default").output.split()
+    generated_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").output.split()
     assert default_checksum[0] != generated_checksum[0]

+ 4 - 4
test/test_ssl/test_wildcard.py

@@ -3,21 +3,21 @@ import pytest
 
 @pytest.mark.parametrize("subdomain", ["foo", "bar"])
 def test_web1_http_redirects_to_https(docker_compose, nginxproxy, subdomain):
-    r = nginxproxy.get("http://%s.nginx-proxy.tld/" % subdomain, allow_redirects=False)
+    r = nginxproxy.get(f"http://{subdomain}.nginx-proxy.tld/", allow_redirects=False)
     assert r.status_code == 301
     assert "Location" in r.headers
-    assert "https://%s.nginx-proxy.tld/" % subdomain == r.headers['Location']
+    assert f"https://{subdomain}.nginx-proxy.tld/" == r.headers['Location']
 
 
 @pytest.mark.parametrize("subdomain", ["foo", "bar"])
 def test_web1_https_is_forwarded(docker_compose, nginxproxy, subdomain):
-    r = nginxproxy.get("https://%s.nginx-proxy.tld/port" % subdomain, allow_redirects=False)
+    r = nginxproxy.get(f"https://{subdomain}.nginx-proxy.tld/port", allow_redirects=False)
     assert r.status_code == 200
     assert "answer from port 81\n" in r.text
 
 
 @pytest.mark.parametrize("subdomain", ["foo", "bar"])
 def test_web1_HSTS_policy_is_active(docker_compose, nginxproxy, subdomain):
-    r = nginxproxy.get("https://%s.nginx-proxy.tld/port" % subdomain, allow_redirects=False)
+    r = nginxproxy.get(f"https://{subdomain}.nginx-proxy.tld/port", allow_redirects=False)
     assert "answer from port 81\n" in r.text
     assert "Strict-Transport-Security" in r.headers

+ 6 - 6
test/test_ssl/wildcard_cert_and_nohttps/test_wildcard_cert_nohttps.py

@@ -1,5 +1,5 @@
 import pytest
-from backports.ssl_match_hostname import CertificateError
+from ssl import CertificateError
 from requests.exceptions import SSLError
 
 
@@ -9,19 +9,19 @@ from requests.exceptions import SSLError
     (3, False),
 ])
 def test_http_redirects_to_https(docker_compose, nginxproxy, subdomain, should_redirect_to_https):
-    r = nginxproxy.get("http://%s.web.nginx-proxy.tld/port" % subdomain)
+    r = nginxproxy.get(f"http://{subdomain}.web.nginx-proxy.tld/port")
     if should_redirect_to_https:
         assert len(r.history) > 0
         assert r.history[0].is_redirect
-        assert r.history[0].headers.get("Location") == "https://%s.web.nginx-proxy.tld/port" % subdomain
-    assert "answer from port 8%s\n" % subdomain == r.text
+        assert r.history[0].headers.get("Location") == f"https://{subdomain}.web.nginx-proxy.tld/port"
+    assert f"answer from port 8{subdomain}\n" == r.text
 
 
 @pytest.mark.parametrize("subdomain", [1, 2])
 def test_https_get_served(docker_compose, nginxproxy, subdomain):
-    r = nginxproxy.get("https://%s.web.nginx-proxy.tld/port" % subdomain, allow_redirects=False)
+    r = nginxproxy.get(f"https://{subdomain}.web.nginx-proxy.tld/port", allow_redirects=False)
     assert r.status_code == 200
-    assert "answer from port 8%s\n" % subdomain == r.text
+    assert f"answer from port 8{subdomain}\n" == r.text
 
 
 def test_web3_https_is_500_and_SSL_validation_fails(docker_compose, nginxproxy):

+ 3 - 3
test/test_wildcard_host.py

@@ -18,9 +18,9 @@ import pytest
     ("web4.whatever.nginx-proxy.regexp", 84),
 ])
 def test_wildcard_prefix(docker_compose, nginxproxy, host, expected_port):
-    r = nginxproxy.get("http://%s/port" % host)
+    r = nginxproxy.get(f"http://{host}/port")
     assert r.status_code == 200
-    assert r.text == "answer from port %s\n" % expected_port
+    assert r.text == f"answer from port {expected_port}\n"
 
 
 @pytest.mark.parametrize("host", [
@@ -28,5 +28,5 @@ def test_wildcard_prefix(docker_compose, nginxproxy, host, expected_port):
     "web4.whatever.nginx-proxy.regexp-to-infinity-and-beyond"
 ])
 def test_non_matching_host_is_503(docker_compose, nginxproxy, host):
-    r = nginxproxy.get("http://%s/port" % host)
+    r = nginxproxy.get(f"http://{host}/port")
     assert r.status_code == 503, r.text