Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul test suite with fixtures #3248

Draft
wants to merge 20 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/nav/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
os.path.join(_venv, 'etc/nav'),
os.path.join(_venv, buildconf.datadir, 'conf'),
] + CONFIG_LOCATIONS
if "NAV_CONFIG_DIR" in os.environ:
CONFIG_LOCATIONS.insert(0, os.environ["NAV_CONFIG_DIR"])


def list_config_files_from_dir(dirname):
Expand Down
6 changes: 6 additions & 0 deletions python/nav/tests/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@
# License along with NAV. If not, see <http://www.gnu.org/licenses/>.
#
"""NAV test cases"""
import pytest
import django.test


class DjangoTransactionTestCase(django.test.TestCase):
serialized_rollback = True

@pytest.fixture(autouse=True)
def requirements(self, postgresql):
"""Ensures the required pytest fixtures are loaded implicitly for all these tests"""
pass
2 changes: 1 addition & 1 deletion python/nav/web/info/room/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def render_netboxes(request, roomid):


@require_http_methods(['POST'])
def create_csv(request):
def create_csv(request) -> HttpResponse:
"""Create csv-file from form data"""
roomname = request.POST.get('roomid', 'room').encode('utf-8')
filename = "{}.csv".format(roomname)
Expand Down
113 changes: 110 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""pytest setup and fixtures common for all tests, regardless of suite"""
"""Pytest config and fixtures for all test suites"""

import os
import platform
Expand All @@ -7,6 +7,10 @@
import pytest
import requests
from requests.adapters import HTTPAdapter, Retry
from retry import retry

from django.core.management import call_command
from django.test import override_settings


def pytest_configure(config):
Expand All @@ -27,6 +31,90 @@ def pytest_configure(config):
install()


def is_running_in_github_actions():
"""Returns True if running under GitHub Actions"""
return os.getenv("GITHUB_ACTIONS")


def _get_preferred_database_name():
if is_running_in_github_actions():
if not os.getenv("PGDATABASE") and os.getenv("TOX_ENV_NAME"):
# Generate an appropriately unique database name for this test run
return "{prefix}_{suffix}".format(
prefix=os.getenv("GITHUB_RUN_ID", "tox"),
suffix=os.getenv("TOX_ENV_NAME").replace("-", "_"),
)
return "nav"


@pytest.fixture(scope='session')
def postgresql(request, admin_username, admin_password):
"""Fixture for all tests that depend on a running PostgreSQL server. This fixture
will try to detect and use an existing PostgreSQL instance (like if running in a
GitHub action), otherwise it will set up a temporary PostgreSQL server for the test
session.

If your test needs to write to the database, it should ask for the `db` fixture
instead, as this ensures changes are rolled back when the test is done. However,
if your test makes db changes that need to be visible from another process, you
must make your own data fixture to ensure the data is removed when the test is
done.
"""
if not is_running_in_github_actions():
request.getfixturevalue("docker_services")

dbname = _get_preferred_database_name()
_update_db_conf_for_test_run(dbname)
_populate_test_database(dbname, admin_username, admin_password)
yield dbname
print("postgres fixture is done")


def _update_db_conf_for_test_run(database_name):
db_conf_path = os.path.join(os.getenv("BUILDDIR"), "etc/db.conf")

pghost = os.getenv('PGHOST', 'localhost')
pgport = os.getenv('PGPORT', '5432')
pguser = os.getenv('PGUSER', 'nav')
pgpassword = os.getenv('PGPASSWORD', 'nav')
with open(db_conf_path, "w") as output:
output.writelines(
[
f"dbhost={pghost}\n",
f"dbport={pgport}\n",
f"db_nav={database_name}\n",
f"script_default={pguser}\n",
f"userpw_{pguser}={pgpassword}\n",
"\n",
]
)
return db_conf_path


@retry(Exception, tries=3, delay=2, backoff=2)
def _populate_test_database(database_name, admin_username, admin_password):
# Init/sync db schema
env = {
'PGHOST': 'localhost',
'PGUSER': 'nav',
'PGDATABASE': database_name,
'PGPORT': '5432',
'PGPASSWORD': 'nav',
'PATH': os.getenv("PATH"),
}
navsyncdb_path = os.path.join(os.getenv("BUILDDIR"), 'bin', 'navsyncdb')
subprocess.check_call([navsyncdb_path], env=env) # , '-c'], #, '--drop-database'],

# reset password of NAV admin account if indicated by environment
if admin_password:
sql = f"UPDATE account SET password = {admin_password!r} WHERE login={admin_username!r}"
subprocess.check_call(["psql", "-c", sql, database_name], env=env)

# Add generic test data set
test_data_path = './tests/docker/scripts/test-data.sql'
subprocess.check_call(["psql", "-f", test_data_path, database_name], env=env)


@pytest.fixture(scope='session')
def admin_username():
return os.environ.get('ADMINUSERNAME', 'admin')
Expand All @@ -38,7 +126,26 @@ def admin_password():


@pytest.fixture(scope='session')
def gunicorn():
def build_sass():
"""Builds the NAV SASS files into CSS files that can be installed as static files"""
subprocess.check_call(["make", "sassbuild"])


@pytest.fixture(scope='session')
def staticfiles(build_sass, tmp_path_factory):
"""Collects Django static files into a temporary directory and return the web root
directory path that can be served by a web server.
"""
webroot = tmp_path_factory.mktemp("webroot")
static = webroot / "static"
with override_settings(STATIC_ROOT=static):
print(f"Collecting static files in {static!r}")
call_command('collectstatic', interactive=False)
yield webroot


@pytest.fixture(scope='session')
def gunicorn(postgresql, staticfiles):
"""Sets up NAV to be served by a gunicorn instance.

Useful for tests that need to make external HTTP requests to NAV.
Expand All @@ -55,7 +162,7 @@ def gunicorn():
errorlog,
'--access-logfile',
accesslog,
'navtest_wsgi:application',
f'navtest_wsgi:nav_test_app(root={str(staticfiles)!r})',
]
)
# Allow for gunicorn to become ready to serve requests before handing off to a test
Expand Down
18 changes: 18 additions & 0 deletions tests/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# This defines external services that the integration tests depend on. Under
# various CI systems, such as GitHub Actions, these services may be provided
# externally. If the test suite deems that they aren't available externally,
# they may be run locally using these definitions instead.
---
version: '3.5'

services:
postgres:
image: docker.io/postgres:11
environment:
- POSTGRES_USER=nav
- POSTGRES_PASSWORD=nav
- POSTGRES_DB=nav
ports:
- 5432:5432
tmpfs:
/var/lib/postgresql/data
60 changes: 0 additions & 60 deletions tests/docker/scripts/create-db.sh

This file was deleted.

20 changes: 0 additions & 20 deletions tests/functional/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import subprocess

import pytest
from selenium.webdriver.common.by import By
Expand All @@ -8,25 +7,6 @@

USERNAME = 'admin'

########################################################################
# #
# Set up the required components for an integration test. Components #
# such as PostgreSQL and Apache are assumed to already be installed on #
# the system. The system is assumed to be Debian. See #
# tests/docker/Dockerfile. #
# #
########################################################################

if os.environ.get('WORKSPACE'):
SCRIPT_PATH = os.path.join(os.environ['WORKSPACE'], 'tests/docker/scripts')
else:
SCRIPT_PATH = '/'
SCRIPT_CREATE_DB = os.path.join(SCRIPT_PATH, 'create-db.sh')


def pytest_configure(config):
subprocess.check_call([SCRIPT_CREATE_DB])


############
# #
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/alertengine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from nav.alertengine.dispatchers import Dispatcher


def test_all_handlers_should_be_loadable():
def test_all_handlers_should_be_loadable(postgresql):
for sender in AlertSender.objects.filter(supported=True):
dispatcher = sender.load_dispatcher_class()
assert issubclass(dispatcher, Dispatcher)
6 changes: 3 additions & 3 deletions tests/integration/auditlog_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.test import TestCase
from nav.tests.cases import DjangoTransactionTestCase

from nav.models.arnold import Justification

Expand All @@ -7,7 +7,7 @@
from nav.auditlog.utils import get_auditlog_entries


class AuditlogModelTestCase(TestCase):
class AuditlogModelTestCase(DjangoTransactionTestCase):
def setUp(self):
# This specific model is used because it is very simple
self.justification = Justification.objects.create(name='testarossa')
Expand Down Expand Up @@ -89,7 +89,7 @@ def test_find_name(self):
self.assertEqual(name, 'blocked_reason')


class AuditlogUtilsTestCase(TestCase):
class AuditlogUtilsTestCase(DjangoTransactionTestCase):
def setUp(self):
# This specific model is used because it is very simple
self.justification = Justification.objects.create(name='testarossa')
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/bin_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
BINDIR = './python/nav/bin'


def test_script_runs(script):
def test_script_runs(postgresql, script):
"""Verifies that a script defined in pyproject.toml runs with a zero exit code"""
if "netbiostracker" in script[0] and not which("nbtscan"):
pytest.skip("nbtscan is not installed")
Expand Down
Loading
Loading