diff options
author | Charles Oliveira <charles.oliveira@linaro.org> | 2020-03-11 16:48:21 -0300 |
---|---|---|
committer | Charles Oliveira <charles.oliveira@linaro.org> | 2020-03-23 19:25:31 -0300 |
commit | 381a89d7517cd7e4f70591d4ea053d72aceedb80 (patch) | |
tree | 4393364fb012cbe4184cc204095a9b02fd4f4776 /tests | |
parent | 3a1aab9581b2ae714f7032d8450f355847a63c83 (diff) |
tests: start local squad server to support testing
Start a local instance of SQUAD so that Squad-Client tests can run
against it.
Add fixtures file, which provides all necessary data to be tested
in squad-client.
The mechanism is pretty simple, before every call to `./manage.py test`,
a fresh squad instance is started up and run that file so that
all data is available thru the api
Diffstat (limited to 'tests')
-rw-r--r-- | tests/__init__.py | 35 | ||||
-rw-r--r-- | tests/fixtures.py | 38 | ||||
-rw-r--r-- | tests/settings.py | 12 | ||||
-rw-r--r-- | tests/squad_service.py | 142 | ||||
-rw-r--r-- | tests/test_api.py | 6 | ||||
-rw-r--r-- | tests/test_models.py | 17 | ||||
-rw-r--r-- | tests/test_shortcuts.py | 7 | ||||
-rw-r--r-- | tests/test_squad_service.py | 69 |
8 files changed, 306 insertions, 20 deletions
diff --git a/tests/__init__.py b/tests/__init__.py index 4b43dfe..159726e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,9 +1,30 @@ -import unittest -import os +import subprocess as sp -def run(): - loader = unittest.TestLoader() - tests = loader.discover(os.path.dirname(os.path.abspath(__file__))) - testRunner = unittest.runner.TextTestRunner() - testRunner.run(tests) +from squad_client.core.api import SquadApi +from .squad_service import SquadService + + +def run(coverage=False, tests=['discover'], verbose=False): + squad_service = SquadService() + if not squad_service.start() or not squad_service.apply_fixtures('tests/fixtures.py'): + print('Aborting tests!') + return False + + SquadApi.configure(url=squad_service.host) + + argv = ['-m', 'unittest'] + tests + if len(tests) == 0 and verbose: + argv += ['discover', '-v'] + + if coverage: + print('\t --coverage is enabled, run `coverage report -m` to view coverage report') + argv = ['coverage', 'run', '--source', 'squad_client'] + argv + else: + argv = ['python3'] + argv + + proc = sp.Popen(argv) + proc.wait() + + squad_service.stop() + return proc.returncode == 0 diff --git a/tests/fixtures.py b/tests/fixtures.py new file mode 100644 index 0000000..53da607 --- /dev/null +++ b/tests/fixtures.py @@ -0,0 +1,38 @@ +# This file is supposed to run in a squad instance for squad-client tests +# +# Some guidance to maintain this file: +# - try to keep all values here +# - do not delete any of the data anywhere (if deletion tests are needed, create one for the specific test) +# + +from squad.core import models as m +from squad.ci import models as mci + +group = m.Group.objects.create(slug='my_group') +group2 = m.Group.objects.create(slug='my_other_group') + +project = group.projects.create(slug='my_project') + +build = project.builds.create(version='my_build') +build2 = project.builds.create(version='my_build2') +build3 = project.builds.create(version='my_build3') +build4 = project.builds.create(version='my_build4') +build5 = project.builds.create(version='my_build5') +build6 = project.builds.create(version='my_build6') + +environment = project.environments.create(slug='my_env') +suite = project.suites.create(slug='my_suite') + +testrun = build.test_runs.create(environment=environment) +passed_test = testrun.tests.create(suite=suite, result=True, name='my_passed_test') +failed_test = testrun.tests.create(suite=suite, result=False, name='my_failed_test') +xfailed_test = testrun.tests.create(suite=suite, result=True, name='my_xfailed_test', has_known_issues=True) +skipped_test = testrun.tests.create(suite=suite, result=None, name='my_skipped_test') + +backend = mci.Backend.objects.create() +testjob = testrun.test_jobs.create(backend=backend, target=project, target_build=build) + +emailtemplate = m.EmailTemplate.objects.create(name='my_emailtemplate') +suitemetadata = m.SuiteMetadata.objects.create(name='my_suitemetadata') +metricthreshold = m.MetricThreshold.objects.create(project=project, value=42) +report = build.delayed_reports.create() diff --git a/tests/settings.py b/tests/settings.py new file mode 100644 index 0000000..ba2153b --- /dev/null +++ b/tests/settings.py @@ -0,0 +1,12 @@ +import os + + +from squad_client.settings import * # noqa + + +tests_dir = os.path.dirname(os.path.abspath(__file__)) + + +DEFAULT_SQUAD_PORT = 9000 +DEFAULT_SQUAD_DATABASE_NAME = os.path.join(tests_dir, 'squad.sqlite3') +DEFAULT_SQUAD_DATABASE_CONFIG = 'ENGINE=django.db.backends.sqlite3:NAME=%s' % DEFAULT_SQUAD_DATABASE_NAME diff --git a/tests/squad_service.py b/tests/squad_service.py new file mode 100644 index 0000000..9a89c32 --- /dev/null +++ b/tests/squad_service.py @@ -0,0 +1,142 @@ +import logging +import os +import subprocess as sp +import time +import requests +import socket + + +from . import settings + + +# Possible outcomes of running squad-admin process +OK = 0 # all good, exited with 0 +TIMEOUT = 1 # as name says, squad-admin timed out +ERROR = 2 # squad-admin didn't time out but returned non-zero status + + +class SquadAdmin: + def __init__(self, env={'DATABASE': settings.DEFAULT_SQUAD_DATABASE_CONFIG}): + self.cmd = ['squad-admin'] + self.env = os.environ.copy() + self.env.update(env) + self.__truncate_database__() + self.logger = logging.getLogger('squad-admin') + self.daemons = [] + + def __del__(self): + if len(self.daemons): + self.logger.info('Terminating daemons') + + for daemon in self.daemons: + if daemon.poll() is None: + daemon.kill() + + def __truncate_database__(self): + if os.path.isfile(settings.DEFAULT_SQUAD_DATABASE_NAME): + os.remove(settings.DEFAULT_SQUAD_DATABASE_NAME) + + def __run_process__(self, args, timeout=10, daemon=False, input=None, stdin=sp.DEVNULL, stdout=sp.DEVNULL, stderr=sp.DEVNULL): + proc = sp.Popen(self.cmd + args, env=self.env, stdin=stdin, stdout=stdout, stderr=stderr) + proc.ok = False + + if not daemon: + try: + proc.out, proc.err = proc.communicate(input=input, timeout=timeout) + proc.ok = (proc.returncode == 0) + except sp.TimeoutExpired: + self.logger.error('Running "%s" time out after %i seconds!' % (' '.join(self.cmd + args), timeout)) + proc.kill() + proc.out, proc.err = proc.communicate() + else: + self.daemons.append(proc) + + return proc + + def migrate(self): + proc = self.__run_process__(['migrate']) + if not proc.ok: + self.logger.error('Failed to migrate!') + return proc + + def runserver(self, port=8000): + # --noreload forces single threaded server. Ref: https://docs.djangoproject.com/en/dev/ref/django-admin/#cmdoption-runserver-noreload + proc = self.__run_process__(['runserver', '--noreload', str(port)], daemon=True) + + attempts = 5 + while attempts > 0: + attempts -= 1 + try: + response = requests.get('http://localhost:%s' % str(port)) + if response.ok: + self.logger.debug('`squad-admin runserver` has started successfully!') + proc.ok = True + except requests.exceptions.ConnectionError: + self.logger.debug('Checking if `squad-admin runserver` is running... attempt %s' % str(attempts)) + time.sleep(1) + + if not proc.ok: + self.logger.error('Failed to start `squad-admin runserver`!') + return proc + + def shell(self, input=None): + proc = self.__run_process__(['shell'], input=input, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE) + return proc + + +class SquadService: + + def __init__(self, port=settings.DEFAULT_SQUAD_PORT): + self.port = port + self.host = 'http://localhost:%s' % str(self.port) + self.service = None + self.squad_admin = SquadAdmin() + self.logger = logging.getLogger('squad-service') + + def __del__(self): + self.stop() + + def __port_in_use__(self): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + return s.connect_ex(('localhost', self.port)) == 0 + + def start(self): + self.logger.info('Starting a fresh squad instance on port %s' % self.port) + + if self.__port_in_use__(): + self.logger.error('... port already in use!') + return False + + self.logger.info('Creating database schema') + proc = self.squad_admin.migrate() + if not proc.ok: + return False + + self.service = self.squad_admin.runserver(port=self.port) + if not self.service.ok: + self.logger.error('Failed to start squad service "%s"' % self.service.stderr.read().decode()) + return False + + return True + + def is_running(self): + return self.service is not None and self.service.poll() is None + + def apply_fixtures(self, fixtures_path): + self.logger.info('Applying %s' % fixtures_path) + + with open(fixtures_path, 'rb') as f: + fixtures_content = f.read() + + proc = self.squad_admin.shell(fixtures_content) + if not proc.ok: + error_message = proc.out + proc.err + self.logger.error(error_message.decode()) + + return proc.ok + + def stop(self): + if self.is_running(): + self.logger.debug('Terminating Squad') + self.service.kill() + self.service.wait(timeout=5) diff --git a/tests/test_api.py b/tests/test_api.py index 82f0656..f2c8763 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,13 +1,13 @@ from unittest import TestCase - +from . import settings from squad_client.core.api import SquadApi, ApiException def is_test_server_running(): import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - result = sock.connect_ex(('localhost', 8000)) + result = sock.connect_ex(('localhost', settings.DEFAULT_SQUAD_PORT)) sock.close() return result == 0 @@ -15,7 +15,7 @@ def is_test_server_running(): class SquadApiTest(TestCase): def setUp(self): - SquadApi.configure(url='http://localhost:8000') + SquadApi.configure(url='http://localhost:%s' % settings.DEFAULT_SQUAD_PORT) def test_malformed_url(self): with self.assertRaises(ApiException): diff --git a/tests/test_models.py b/tests/test_models.py index 072b08a..25bd597 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,12 +1,12 @@ import unittest - +from . import settings from squad_client.core.api import SquadApi -from squad_client.core.models import Squad +from squad_client.core.models import Squad, ALL from squad_client.utils import first -SquadApi.configure(url='http://localhost:8000') +SquadApi.configure(url='http://localhost:%s' % settings.DEFAULT_SQUAD_PORT) class SquadTest(unittest.TestCase): @@ -23,16 +23,19 @@ class SquadTest(unittest.TestCase): self.assertEqual(0, len(groups)) def test_groups_with_count(self): - four_groups = self.squad.groups(count=4) - self.assertEqual(4, len(four_groups)) + all_groups = self.squad.groups(count=ALL) + self.assertEqual(2, len(all_groups)) + + one_groups = self.squad.groups(count=1) + self.assertEqual(1, len(one_groups)) def test_not_found_group(self): not_found_group = self.squad.group('this-group-does-not-really-exist') self.assertEqual(None, not_found_group) def test_group(self): - lkft_group = self.squad.group('lkft') - self.assertTrue(lkft_group is not None) + group = self.squad.group('my_group') + self.assertTrue(group is not None) def test_projects(self): projects = self.squad.projects() diff --git a/tests/test_shortcuts.py b/tests/test_shortcuts.py index 4d5342a..b635a4b 100644 --- a/tests/test_shortcuts.py +++ b/tests/test_shortcuts.py @@ -1,6 +1,7 @@ from unittest import TestCase +from . import settings from squad_client.core.api import SquadApi from squad_client.shortcuts import retrieve_latest_builds, retrieve_build_results @@ -8,12 +9,12 @@ from squad_client.shortcuts import retrieve_latest_builds, retrieve_build_result class ShortcutsTest(TestCase): def setUp(self): - SquadApi.configure('http://localhost:8000') + SquadApi.configure('http://localhost:%s' % settings.DEFAULT_SQUAD_PORT) def test_retrieve_latest_builds(self): - builds = retrieve_latest_builds('lkft/linux-stable-rc-4.14-oe', count=5) + builds = retrieve_latest_builds('my_group/my_project', count=5) self.assertEqual(5, len(builds)) def test_retrieve_build_results(self): - results = retrieve_build_results('lkft/linux-stable-rc-4.14-oe-sanity/build/v4.14.74') + results = retrieve_build_results('my_group/my_project/build/my_build') self.assertIsNotNone(results) diff --git a/tests/test_squad_service.py b/tests/test_squad_service.py new file mode 100644 index 0000000..4d3aa30 --- /dev/null +++ b/tests/test_squad_service.py @@ -0,0 +1,69 @@ +from unittest import TestCase +import uuid + + +from .squad_service import SquadAdmin, SquadService + + +DB_NAME = str(uuid.uuid4()) + + +class SquadServiceTest(TestCase): + + def setUp(self): + self.squad_service = SquadService(port=9001) + self.squad_service.squad_admin.env['DATABASE'] = 'ENGINE=django.db.backends.sqlite3:NAME=/tmp/%s.sqlite3' % DB_NAME + + def test_start(self): + self.squad_service.start() + self.assertTrue(self.squad_service.is_running()) + + self.squad_service.stop() + self.assertFalse(self.squad_service.is_running()) + + def test_fixtures(self): + self.squad_service.start() + self.assertTrue(self.squad_service.is_running()) + + ok = self.squad_service.apply_fixtures('tests/fixtures.py') + self.assertTrue(ok) + + self.squad_service.stop() + self.assertFalse(self.squad_service.is_running()) + + +class SquadAdminTest(TestCase): + + def setUp(self): + self.squad_admin = SquadAdmin() + self.squad_admin.env['DATABASE'] = 'ENGINE=django.db.backends.sqlite3:NAME=/tmp/%s.sqlite3' % DB_NAME + + def tearDown(self): + self.squad_admin.__truncate_database__() + + def test_migration(self): + status = self.squad_admin.migrate() + self.assertTrue(status.ok) + + def test_runserver(self): + migrate_status = self.squad_admin.migrate() + self.assertTrue(migrate_status.ok) + + service = self.squad_admin.runserver(port=9002) + self.assertTrue(service.ok) + + service.kill() + service.wait(timeout=5) + + def test_shell(self): + migrate_status = self.squad_admin.migrate() + self.assertTrue(migrate_status.ok) + + shell_status = self.squad_admin.shell(b'print(') + self.assertFalse(shell_status.ok) + + shell_status = self.squad_admin.shell(b'print()') + self.assertTrue(shell_status.ok) + + shell_status = self.squad_admin.shell(b'from squad.core.models import Group; Group.objects.create(slug="agroup")') + self.assertTrue(shell_status.ok) |