From 986f37b017134ced5d9dd38b420350916297002b Mon Sep 17 00:00:00 2001
From: Daniel Watkins <oddbloke@ubuntu.com>
Date: Tue, 10 Mar 2020 13:26:05 -0400
Subject: [PATCH] cloudinit: move to pytest for running tests (#211)

As the nose docs[0] themselves note, it has been in maintenance mode for the past several years. pytest is an actively developed, featureful and popular alternative that the nose docs themselves recommend. See [1] for more details about the thinking here.

(This PR also removes stale tox definitions, instead of modifying them.)

[0] https://nose.readthedocs.io/en/latest/
[1] https://lists.launchpad.net/cloud-init/msg00245.html
---
 .travis.yml            |  6 ++--
 Makefile               |  6 ++--
 packages/pkg-deps.json |  3 ++
 test-requirements.txt  |  7 ++--
 tools/run-container    | 12 +++----
 tools/tox-venv         |  2 +-
 tox.ini                | 73 +++++++++++++++++-------------------------
 7 files changed, 46 insertions(+), 63 deletions(-)

--- a/.travis.yml
+++ b/.travis.yml
@@ -16,13 +16,13 @@ matrix:
         - python: 3.6
           env:
               TOXENV=py3
-              NOSE_VERBOSE=2  # List all tests run by nose
+              PYTEST_ADDOPTS=-v  # List all tests run by pytest
         - install:
             - git fetch --unshallow
             - sudo apt-get build-dep -y cloud-init
             - sudo apt-get install -y --install-recommends sbuild ubuntu-dev-tools fakeroot tox
             # These are build deps but not pulled in by the build-dep call above
-            - sudo apt-get install -y --install-recommends dh-systemd python3-coverage python3-contextlib2
+            - sudo apt-get install -y --install-recommends dh-systemd python3-coverage python3-contextlib2 python3-pytest python3-pytest-cov
             - pip install .
             - pip install tox
             # bionic has lxd from deb installed, remove it first to ensure
@@ -46,7 +46,7 @@ matrix:
         - python: 3.5
           env:
               TOXENV=xenial
-              NOSE_VERBOSE=2  # List all tests run by nose
+              PYTEST_ADDOPTS=-v  # List all tests run by pytest
           # Travis doesn't support Python 3.4 on bionic, so use xenial
           dist: xenial
         - python: 3.6
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,6 @@ CWD=$(shell pwd)
 PYVER ?= $(shell for p in python3 python2; do \
 	out=$$(command -v $$p 2>&1) && echo $$p && exit; done; exit 1)
 
-noseopts ?= -v
-
 YAML_FILES=$(shell find cloudinit tests tools -name "*.yaml" -type f )
 YAML_FILES+=$(shell find doc/examples -name "cloud-config*.txt" -type f )
 
@@ -48,10 +46,10 @@ pyflakes3:
 	@$(CWD)/tools/run-pyflakes3
 
 unittest: clean_pyc
-	nosetests $(noseopts) tests/unittests cloudinit
+	python -m pytest -v tests/unittests cloudinit
 
 unittest3: clean_pyc
-	nosetests3 $(noseopts) tests/unittests cloudinit
+	python3 -m pytest -v tests/unittests cloudinit
 
 ci-deps-ubuntu:
 	@$(PYVER) $(CWD)/tools/read-dependencies --distro ubuntu --test-distro
--- a/packages/pkg-deps.json
+++ b/packages/pkg-deps.json
@@ -45,6 +45,9 @@
          "pyserial" : {
             "2" : "pyserial"
          },
+         "pytest": {
+             "3": "python36-pytest"
+         },
          "requests" : {
             "3" : "python36-requests"
          },
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,11 +1,8 @@
 # Needed generally in tests
 httpretty>=0.7.1
-nose
+pytest
 unittest2
-coverage
-
-# Only needed if you want to know the test times
-# nose-timer
+pytest-cov
 
 # Only really needed on older versions of python
 contextlib2
--- a/tools/run-container
+++ b/tools/run-container
@@ -287,8 +287,8 @@ prep() {
     install_packages "$@"
 }
 
-nose() {
-    python3 -m nose "$@"
+pytest() {
+    python3 -m pytest "$@"
 }
 
 is_done_cloudinit() {
@@ -478,10 +478,10 @@ main() {
 
     if [ -n "$unittest" ]; then
         debug 1 "running unit tests."
-        run_self_inside_as_cd "$name" "$user" "$cdir" nose \
+        run_self_inside_as_cd "$name" "$user" "$cdir" pytest \
             tests/unittests cloudinit/ || {
-                errorrc "nosetests failed.";
-                errors[${#errors[@]}]="nosetests"
+                errorrc "pytest failed.";
+                errors[${#errors[@]}]="pytest"
             }
     fi
 
@@ -557,7 +557,7 @@ main() {
 }
 
 case "${1:-}" in
-    prep|os_info|wait_inside|nose) _n=$1; shift; "$_n" "$@";;
+    prep|os_info|wait_inside|pytest) _n=$1; shift; "$_n" "$@";;
     *) main "$@";;
 esac
 
--- a/tools/tox-venv
+++ b/tools/tox-venv
@@ -116,7 +116,7 @@ Usage: ${0##*/} [--no-create] tox-enviro
    be read from tox.ini.  This allows you to do:
       tox-venv py27 - tests/some/sub/dir
    and have the 'command' read correctly and have that execute:
-      python -m nose tests/some/sub/dir
+      python -m pytest tests/some/sub/dir
 EOF
 
     if [ -f "$tox_ini" ]; then
--- a/tox.ini
+++ b/tox.ini
@@ -1,13 +1,13 @@
 [tox]
-envlist = py3, xenial, pycodestyle, pyflakes, pylint
+envlist = py3, xenial-dev, pycodestyle, pyflakes, pylint
 recreate = True
 
 [testenv]
-commands = python -m nose {posargs:tests/unittests cloudinit}
+commands = {envpython} -m pytest {posargs:tests/unittests cloudinit}
 setenv =
     LC_ALL = en_US.utf-8
 passenv=
-    NOSE_VERBOSE
+    PYTEST_ADDOPTS
 
 [testenv:pycodestyle]
 basepython = python3
@@ -32,23 +32,16 @@ commands = {envpython} -m pylint {posarg
 [testenv:py3]
 basepython = python3
 deps =
-    nose-timer
     -r{toxinidir}/test-requirements.txt
-commands = {envpython} -m nose --with-timer --timer-top-n 10 \
-           {posargs:--with-coverage --cover-erase --cover-branches \
-            --cover-inclusive --cover-package=cloudinit \
+commands = {envpython} -m pytest \
+            --durations 10 \
+            {posargs:--cov=cloudinit --cov-branch \
             tests/unittests cloudinit}
 
 [testenv:py27]
 basepython = python2.7
 deps = -r{toxinidir}/test-requirements.txt
 
-[testenv:py26]
-deps = -r{toxinidir}/test-requirements.txt
-commands = nosetests {posargs:tests/unittests cloudinit}
-setenv =
-    LC_ALL = C
-
 [flake8]
 #H102  Apache 2.0 license header not found
 ignore=H404,H405,H105,H301,H104,H403,H101,H102,H106,H304
@@ -62,11 +55,15 @@ commands =
     {envpython} -m sphinx {posargs:doc/rtd doc/rtd_html}
     doc8 doc/rtd
 
-[testenv:xenial]
-commands =
-  python ./tools/pipremove jsonschema
-  python -m nose {posargs:tests/unittests cloudinit}
-basepython = python3
+[xenial-shared-deps]
+# The version of pytest in xenial doesn't work with Python 3.8, so we define
+# two xenial environments: [testenv:xenial] runs the tests with exactly the
+# version of pytest present in xenial, and is used in CI.  [testenv:xenial-dev]
+# runs the tests with the lowest version of pytest that works with Python 3.8,
+# 3.0.7, but keeps the other dependencies at xenial's level.
+#
+# (This section is not a testenv, it is used to maintain a single definition of
+# the dependencies shared between the two xenial testenvs.)
 deps =
     # requirements
     jinja2==2.8
@@ -83,38 +80,26 @@ deps =
     # test-requirements
     httpretty==0.9.6
     mock==1.3.0
-    nose==1.3.7
     unittest2==1.1.0
     contextlib2==0.5.1
 
-[testenv:centos6]
-basepython = python2.6
-commands = nosetests {posargs:tests/unittests cloudinit}
-deps =
-    # requirements
-    argparse==1.2.1
-    jinja2==2.2.1
-    pyyaml==3.10
-    oauthlib==0.6.0
-    configobj==4.6.0
-    requests==2.6.0
-    jsonpatch==1.2
-    six==1.9.0
-    -r{toxinidir}/test-requirements.txt
-
-[testenv:opensusel150]
-basepython = python2.7
-commands = nosetests {posargs:tests/unittests cloudinit}
+[testenv:xenial]
+commands =
+  python ./tools/pipremove jsonschema
+  python -m pytest {posargs:tests/unittests cloudinit}
+basepython = python3
 deps =
-    # requirements
-    jinja2==2.10
-    PyYAML==3.12
-    oauthlib==2.0.6
-    configobj==5.0.6
-    requests==2.18.4
-    jsonpatch==1.16
-    six==1.11.0
-    -r{toxinidir}/test-requirements.txt
+    # Refer to the comment in [xenial-shared-deps] for details
+    {[xenial-shared-deps]deps}
+    pytest==2.8.7
+
+[testenv:xenial-dev]
+commands = {[testenv:xenial]commands}
+basepython = {[testenv:xenial]basepython}
+deps =
+    # Refer to the comment in [xenial-shared-deps] for details
+    {[xenial-shared-deps]deps}
+    pytest==3.0.7
 
 [testenv:tip-pycodestyle]
 commands = {envpython} -m pycodestyle {posargs:cloudinit/ tests/ tools/}
