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 #2675

Draft
wants to merge 32 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
35287e6
Add ugly hack for netsnmp import on MacOS
lunkwill42 Feb 9, 2023
24b1eef
Remove obsolete future import
lunkwill42 Feb 10, 2023
f490b1f
Make gunicorn a session fixture
lunkwill42 Feb 10, 2023
941a63a
Install custom epollreactor for tests on Linux
lunkwill42 Feb 10, 2023
7bea27f
Replace create-db.sh with PostgreSQL fixtures
lunkwill42 Feb 10, 2023
ca1eaf9
Make other database fixtures dependent on Postgres
lunkwill42 Feb 10, 2023
e638e90
Consolidate gunicorn fixtures
lunkwill42 Feb 10, 2023
8b69e29
Make DjangoTransactionTestCase depend on postgres
lunkwill42 Feb 21, 2023
ee507a8
Inherit DjangoTransactionTestCase
lunkwill42 Feb 21, 2023
2579e96
Make tests properly depend on postgresql
lunkwill42 Feb 21, 2023
16b71ad
Make client test fixture depend on postgresql
lunkwill42 Feb 21, 2023
d8c1574
Make snmpsim fixture work portably
lunkwill42 Feb 21, 2023
8d34102
Skip pping tests if timeout command is unavailable
lunkwill42 Feb 21, 2023
4b30c0e
Move imports from global to local
lunkwill42 Feb 21, 2023
0dcad4f
Add TODO for the admin_navlet fixture
lunkwill42 Feb 21, 2023
89179f8
Fix broken statemon tests
lunkwill42 Jun 5, 2023
59f42e8
"Un-parametrize" test_get_navlet_should_return_200
lunkwill42 Jun 5, 2023
a8bc7b5
"Un-parametrize" webcrawler tests
lunkwill42 Jun 5, 2023
161fc8a
Make gunicorn fixture depend on postgresql
lunkwill42 Jun 5, 2023
0f92758
Make fixtures out of web access config
lunkwill42 Jun 5, 2023
06db6bb
Test web server reachability from fixture
lunkwill42 Jun 5, 2023
a85d903
Skip validation tests when tidylib is missing
lunkwill42 Jun 5, 2023
a6093c8
Skip netbiostracker binary test
lunkwill42 Jun 5, 2023
cf78590
Add snmpsim as required tool for test suite
lunkwill42 Jun 5, 2023
d06737e
Properly mark tests as wanting postgresql
lunkwill42 Jun 5, 2023
5205fe3
Add usage tips to docstring
lunkwill42 Jun 5, 2023
93c24ef
Introduce pytest mark for really slow tests
lunkwill42 Jun 5, 2023
7442e72
Fix non-interactive sudo test
lunkwill42 Jun 6, 2023
f11572a
Fix broken skip logic
lunkwill42 Jun 6, 2023
2cf4e42
Pass C_INCLUDE_PATH to test environment
lunkwill42 Sep 5, 2023
b21a159
Leave out optional requirements from test env
lunkwill42 Sep 5, 2023
da5c940
Skip LDAP tests if LDAP library isn't available
lunkwill42 Dec 8, 2023
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
8 changes: 8 additions & 0 deletions python/nav/Snmp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

"""
from __future__ import absolute_import
import os
import sys

BACKEND = None

Expand All @@ -35,6 +37,12 @@
# These wildcard imports are informed, not just accidents.
# pylint: disable=W0401
if BACKEND == 'pynetsnmp':
if sys.platform == "darwin" and not os.getenv("DYLD_LIBRARY_PATH"):
# horrible workaround for MacOS problems, described at length at
# https://hynek.me/articles/macos-dyld-env/
os.environ["DYLD_LIBRARY_PATH"] = os.getenv(
"LD_LIBRARY_PATH", "/usr/local/opt/openssl/lib"
)
from .pynetsnmp import *
else:
raise ImportError("No supported SNMP backend was found")
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
129 changes: 129 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""Pytest config and fixtures for all test suites"""
import os
import subprocess

import pytest
from retry import retry
import requests


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):
"""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)
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):
# 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
adminpassword = os.getenv("ADMINPASSWORD")
if adminpassword:
sql = f"UPDATE account SET password = {adminpassword!r} WHERE login='admin'"
subprocess.check_call(["psql", "-c", sql, database_name])

# 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 gunicorn(postgresql):
workspace = os.path.join(os.environ.get('WORKSPACE', ''), 'reports')
errorlog = os.path.join(workspace, 'gunicorn-error.log')
accesslog = os.path.join(workspace, 'gunicorn-access.log')
gunicorn = subprocess.Popen(
[
'gunicorn',
'--error-logfile',
errorlog,
'--access-logfile',
accesslog,
'navtest_wsgi:application',
]
)
# Fire off an initial request to ensure the webserver is actually running
response = requests.get("http://localhost:8000/")
assert response.status_code == 200, response.content
yield gunicorn
gunicorn.terminate()


@pytest.fixture(scope='session')
def web_target_url():
yield "http://localhost:8000/"


@pytest.fixture(scope='session')
def web_admin_username():
yield "admin"


@pytest.fixture(scope='session')
def web_admin_password():
yield "admin"
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.

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

import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

USERNAME = 'admin'
gunicorn = None

########################################################################
# #
# 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])
start_gunicorn()


def pytest_unconfigure(config):
stop_gunicorn()


def start_gunicorn():
global gunicorn
gunicorn_log = open("reports/gunicorn.log", "ab")
gunicorn = subprocess.Popen(
['gunicorn', 'navtest_wsgi:application'],
stdout=gunicorn_log,
stderr=subprocess.STDOUT,
)


def stop_gunicorn():
if gunicorn:
gunicorn.terminate()


############
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 @@ -80,7 +80,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
10 changes: 7 additions & 3 deletions tests/integration/bin_test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import pytest
import sys
from shutil import which
import subprocess

import pytest


def test_binary_runs(binary):
def test_binary_runs(postgresql, binary):
"""Verifies that a command runs with a zero exit code"""
if "netbiostracker" in binary[0] and not which("nbtscan"):
pytest.skip("nbtscan is not installed")

proc = subprocess.Popen(binary, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
(done, fail) = proc.communicate()
retcode = proc.wait()
Expand Down
Loading
Loading