From 8d90596f30b080e5657fccc6959914ba3c38b08c Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Wed, 3 Aug 2016 14:51:38 +0100 Subject: [PATCH 1/9] added root_dir to set where the runner looks for steps to import in djnago 1.9 this causes the models.py file to attempt import causing an error like: RuntimeError: Model class models. doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS. --- lettuce/django/management/commands/harvest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lettuce/django/management/commands/harvest.py b/lettuce/django/management/commands/harvest.py index 4ac86ea08..34a032e6b 100644 --- a/lettuce/django/management/commands/harvest.py +++ b/lettuce/django/management/commands/harvest.py @@ -227,6 +227,7 @@ def handle(self, *args, **options): subunit_filename=options.get('subunit_file'), jsonreport_filename=options.get('jsonreport_file'), tags=tags, failfast=failfast, auto_pdb=auto_pdb, + root_dir=path, smtp_queue=smtp_queue) result = runner.run() From 1bc7d3fee99235417a85872e3c4f131256dbf9a7 Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Fri, 5 Aug 2016 13:52:50 +0100 Subject: [PATCH 2/9] Added fix for when you call harvest with a single app name fixes incorrect search for modules in apps main folder instead of the features folder --- lettuce/django/management/commands/harvest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lettuce/django/management/commands/harvest.py b/lettuce/django/management/commands/harvest.py index 34a032e6b..33f93540a 100644 --- a/lettuce/django/management/commands/harvest.py +++ b/lettuce/django/management/commands/harvest.py @@ -143,12 +143,12 @@ def stopserver(self, failed=False): def get_paths(self, args, apps_to_run, apps_to_avoid): if args: + paths = [] for path, exists in zip(args, map(os.path.exists, args)): if not exists: sys.stderr.write("You passed the path '%s', but it does not exist.\n" % path) sys.exit(1) - else: - paths = args + paths.append(os.path.join(os.getcwd(), path, 'features')) else: paths = harvest_lettuces(apps_to_run, apps_to_avoid) # list of tuples with (path, app_module) From 80f74751acf3c7e870ead83cd89b17c0b05cc0a6 Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Thu, 22 Sep 2016 16:05:40 +0100 Subject: [PATCH 3/9] Update models.py --- lettuce/django/steps/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lettuce/django/steps/models.py b/lettuce/django/steps/models.py index f4aa31092..75c410a05 100644 --- a/lettuce/django/steps/models.py +++ b/lettuce/django/steps/models.py @@ -8,7 +8,13 @@ from django.core.management import call_command from django.core.management.color import no_style from django.db import connection -from django.db.models.loading import get_models + +try: + from django.apps import apps + get_models = apps.get_models +except ImportError: + from django.db.models.loading import get_models + from django.utils.functional import curry from functools import wraps From b44572731487579d58cb84f5e81d590280c1decb Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Sat, 24 Sep 2016 14:22:59 +0100 Subject: [PATCH 4/9] updated to latest lettuce enhancements --- lettuce/__init__.py | 21 +- lettuce/django/apps.py | 8 +- lettuce/django/management/commands/harvest.py | 236 +++++++++--------- lettuce/django/steps/models.py | 6 +- lettuce/exceptions.py | 4 + 5 files changed, 130 insertions(+), 145 deletions(-) diff --git a/lettuce/__init__.py b/lettuce/__init__.py index d4b73cd7d..674507191 100644 --- a/lettuce/__init__.py +++ b/lettuce/__init__.py @@ -22,7 +22,6 @@ import os import sys import traceback -import warnings try: from imp import reload except ImportError: @@ -41,7 +40,7 @@ from lettuce.registry import call_hook from lettuce.registry import STEP_REGISTRY from lettuce.registry import CALLBACK_REGISTRY -from lettuce.exceptions import StepLoadingError +from lettuce.exceptions import StepLoadingError, LettuceRunnerError from lettuce.plugins import ( xunit_output, subunit_output, @@ -81,7 +80,7 @@ sys.stderr.write(string) sys.stderr.write(exceptions.traceback.format_exc(e)) - raise SystemExit(1) + raise LettuceRunnerError(string) class Runner(object): @@ -125,17 +124,11 @@ def __init__(self, base_path, scenarios=None, from lettuce.plugins import dots as output elif verbosity is 2: from lettuce.plugins import scenario_names as output - else: - if verbosity is 4: + elif verbosity is 3: + if no_color: + from lettuce.plugins import shell_output as output + else: from lettuce.plugins import colored_shell_output as output - msg = ('Deprecated in lettuce 2.2.21. Use verbosity 3 without ' - '--no-color flag instead of verbosity 4') - warnings.warn(msg, DeprecationWarning) - elif verbosity is 3: - if no_color: - from lettuce.plugins import shell_output as output - else: - from lettuce.plugins import colored_shell_output as output self.random = random @@ -215,6 +208,6 @@ def run(self): call_hook('after', 'all', total) if failed: - raise SystemExit(2) + raise LettuceRunnerError("Test failed.") return total diff --git a/lettuce/django/apps.py b/lettuce/django/apps.py index e518c6fac..0ca18407a 100644 --- a/lettuce/django/apps.py +++ b/lettuce/django/apps.py @@ -32,7 +32,7 @@ def _filter_bultins(module): def _filter_configured_apps(module): "returns only those apps that are in django.conf.settings.LETTUCE_APPS" app_found = True - if hasattr(settings, 'LETTUCE_APPS') and isinstance(settings.LETTUCE_APPS, tuple): + if hasattr(settings, 'LETTUCE_APPS') and isinstance(settings.LETTUCE_APPS, (list, tuple)): app_found = False for appname in settings.LETTUCE_APPS: if module.__name__.startswith(appname): @@ -44,7 +44,7 @@ def _filter_configured_apps(module): def _filter_configured_avoids(module): "returns apps that are not within django.conf.settings.LETTUCE_AVOID_APPS" run_app = False - if hasattr(settings, 'LETTUCE_AVOID_APPS') and isinstance(settings.LETTUCE_AVOID_APPS, tuple): + if hasattr(settings, 'LETTUCE_AVOID_APPS') and isinstance(settings.LETTUCE_AVOID_APPS, (list, tuple)): for appname in settings.LETTUCE_AVOID_APPS: if module.__name__.startswith(appname): run_app = True @@ -73,7 +73,7 @@ def harvest_lettuces(only_the_apps=None, avoid_apps=None, path="features"): apps = get_apps() - if isinstance(only_the_apps, tuple) and any(only_the_apps): + if isinstance(only_the_apps, (list, tuple)) and any(only_the_apps): def _filter_only_specified(module): return module.__name__ in only_the_apps @@ -83,7 +83,7 @@ def _filter_only_specified(module): apps = filter(_filter_configured_apps, apps) apps = filter(_filter_configured_avoids, apps) - if isinstance(avoid_apps, tuple) and any(avoid_apps): + if isinstance(avoid_apps, (list, tuple)) and any(avoid_apps): def _filter_avoid(module): return module.__name__ not in avoid_apps diff --git a/lettuce/django/management/commands/harvest.py b/lettuce/django/management/commands/harvest.py index 33f93540a..c224f1fec 100644 --- a/lettuce/django/management/commands/harvest.py +++ b/lettuce/django/management/commands/harvest.py @@ -16,130 +16,121 @@ # along with this program. If not, see . import os import sys -import django +import traceback from distutils.version import StrictVersion -from optparse import make_option + +import django from django.conf import settings from django.core.management import call_command -from django.core.management.base import BaseCommand -from django.test.utils import setup_test_environment -from django.test.utils import teardown_test_environment +from django.core.management.base import BaseCommand, CommandError +from django.test.utils import setup_test_environment, teardown_test_environment from lettuce import Runner from lettuce import registry from lettuce.core import SummaryTotalResults +from lettuce.exceptions import LettuceRunnerError from lettuce.django import harvest_lettuces, get_server from lettuce.django.server import LettuceServerException +DJANGO_VERSION = StrictVersion(django.get_version()) + + class Command(BaseCommand): help = u'Run lettuce tests all along installed apps' args = '[PATH to feature file or folder]' - requires_model_validation = False - - option_list = BaseCommand.option_list + ( - make_option('-a', '--apps', action='store', dest='apps', default='', - help='Run ONLY the django apps that are listed here. Comma separated'), - - make_option('-A', '--avoid-apps', action='store', dest='avoid_apps', default='', - help='AVOID running the django apps that are listed here. Comma separated'), - make_option('-S', '--no-server', action='store_true', dest='no_server', default=False, - help="will not run django's builtin HTTP server"), - - make_option('--nothreading', action='store_false', dest='use_threading', default=True, - help='Tells Django to NOT use threading.'), - - make_option('-T', '--test-server', action='store_true', dest='test_database', + if DJANGO_VERSION < StrictVersion('1.7'): + requires_model_validation = False + else: + requires_system_checks = False + + def add_arguments(self, parser): + parser.set_defaults(verbosity=3) # default verbosity is 3 + parser.add_argument( + '-a', '--apps', action='store', dest='apps', default='', + help='Run ONLY the django apps that are listed here. Comma separated' + ) + parser.add_argument( + '-A', '--avoid-apps', action='store', dest='avoid_apps', default='', + help='AVOID running the django apps that are listed here. Comma separated' + ) + parser.add_argument( + '-S', '--no-server', action='store_true', dest='no_server', default=False, + help="will not run django's builtin HTTP server" + ) + parser.add_argument( + '--nothreading', action='store_false', dest='use_threading', default=True, + help='Tells Django to NOT use threading.' + ) + parser.add_argument( + '-T', '--test-server', action='store_true', dest='test_database', default=getattr(settings, "LETTUCE_USE_TEST_DATABASE", False), - help="will run django's builtin HTTP server using the test databases"), - - make_option('-P', '--port', type='int', dest='port', - help="the port in which the HTTP server will run at"), - - make_option('-d', '--debug-mode', action='store_true', dest='debug', default=False, - help="when put together with builtin HTTP server, forces django to run with settings.DEBUG=True"), - - make_option('-s', '--scenarios', action='store', dest='scenarios', default=None, - help='Comma separated list of scenarios to run'), - - make_option("-t", "--tag", - dest="tags", - type="str", - action='append', - default=None, - help='Tells lettuce to run the specified tags only; ' - 'can be used multiple times to define more tags' - '(prefixing tags with "-" will exclude them and ' - 'prefixing with "~" will match approximate words)'), - - make_option('--with-xunit', action='store_true', dest='enable_xunit', default=False, - help='Output JUnit XML test results to a file'), - - make_option('--smtp-queue', action='store_true', dest='smtp_queue', default=False, - help='Use smtp for mail queue (usefull with --no-server option'), - - make_option('--xunit-file', action='store', dest='xunit_file', default=None, - help='Write JUnit XML to this file. Defaults to lettucetests.xml'), - - make_option('--with-subunit', - action='store_true', - dest='enable_subunit', - default=False, - help='Output Subunit test results to a file'), - - make_option('--subunit-file', - action='store', - dest='subunit_file', - default=None, - help='Write Subunit to this file. Defaults to subunit.bin'), - - make_option('--with-jsonreport', - action='store_true', - dest='enable_jsonreport', - default=False, - help='Output JSON test results to a file'), - - make_option('--jsonreport-file', - action='store', - dest='jsonreport_file', - default=None, - help='Write JSON report to this file. Defaults to lettucetests.json'), - - make_option("--failfast", dest="failfast", default=False, - action="store_true", help='Stop running in the first failure'), - - make_option("--pdb", dest="auto_pdb", default=False, - action="store_true", help='Launches an interactive debugger upon error'), - - ) - - def create_parser(self, prog_name, subcommand): - parser = super(Command, self).create_parser(prog_name, subcommand) - parser.remove_option('-v') - help_text = ('Verbosity level; 0=no output, 1=only dots, 2=only ' - 'scenario names, 3=normal output, 4=normal output ' - '(colorful, deprecated)') - parser.add_option('-v', '--verbosity', - action='store', - dest='verbosity', - default='3', - type='choice', - choices=map(str, range(5)), - help=help_text) - if StrictVersion(django.get_version()) < StrictVersion('1.7'): + help="will run django's builtin HTTP server using the test databases" + ) + parser.add_argument( + '-P', '--port', type=int, dest='port', + help="the port in which the HTTP server will run at" + ) + parser.add_argument( + '-d', '--debug-mode', action='store_true', dest='debug', default=False, + help="when put together with builtin HTTP server, forces django to run with settings.DEBUG=True" + ) + parser.add_argument( + '-s', '--scenarios', action='store', dest='scenarios', default=None, + help='Comma separated list of scenarios to run' + ) + parser.add_argument( + "-t", "--tag", dest="tags", type=str, action='append', default=None, + help='Tells lettuce to run the specified tags only; ' + 'can be used multiple times to define more tags' + '(prefixing tags with "-" will exclude them and ' + 'prefixing with "~" will match approximate words)' + ) + parser.add_argument( + '--with-xunit', action='store_true', dest='enable_xunit', default=False, + help='Output JUnit XML test results to a file' + ) + parser.add_argument( + '--smtp-queue', action='store_true', dest='smtp_queue', default=False, + help='Use smtp for mail queue (usefull with --no-server option' + ) + parser.add_argument( + '--xunit-file', action='store', dest='xunit_file', default=None, + help='Write JUnit XML to this file. Defaults to lettucetests.xml' + ) + parser.add_argument( + '--with-subunit', action='store_true', dest='enable_subunit', + default=False, help='Output Subunit test results to a file' + ) + parser.add_argument( + '--subunit-file', action='store', dest='subunit_file', default=None, + help='Write Subunit to this file. Defaults to subunit.bin' + ) + parser.add_argument( + '--with-jsonreport', action='store_true', dest='enable_jsonreport', + default=False, help='Output JSON test results to a file' + ) + parser.add_argument( + '--jsonreport-file', action='store', dest='jsonreport_file', + default=None, help='Write JSON report to this file. Defaults to lettucetests.json' + ) + parser.add_argument( + "--failfast", dest="failfast", default=False, + action="store_true", help='Stop running in the first failure' + ) + parser.add_argument( + "--pdb", dest="auto_pdb", default=False, action="store_true", + help='Launches an interactive debugger upon error' + ) + if DJANGO_VERSION < StrictVersion('1.7'): # Django 1.7 introduces the --no-color flag. We must add the flag # to be compatible with older django versions - parser.add_option('--no-color', - action='store_true', - dest='no_color', - default=False, - help="Don't colorize the command output.") - return parser - - def stopserver(self, failed=False): - raise SystemExit(int(failed)) + parser.add_argument( + '--no-color', action='store_true', dest='no_color', + default=False, help="Don't colorize the command output." + ) def get_paths(self, args, apps_to_run, apps_to_avoid): if args: @@ -157,18 +148,17 @@ def get_paths(self, args, apps_to_run, apps_to_avoid): def handle(self, *args, **options): setup_test_environment() - verbosity = int(options.get('verbosity', 3)) - no_color = int(options.get('no_color', False)) - apps_to_run = tuple(options.get('apps', '').split(",")) - apps_to_avoid = tuple(options.get('avoid_apps', '').split(",")) - run_server = not options.get('no_server', False) - test_database = options.get('test_database', False) - smtp_queue = options.get('smtp_queue', False) - tags = options.get('tags', None) - failfast = options.get('failfast', False) - auto_pdb = options.get('auto_pdb', False) - threading = options.get('use_threading', True) - with_summary = options.get('summary_display', False) + verbosity = options['verbosity'] + no_color = options.get('no_color', False) + apps_to_run = tuple(options['apps'].split(",")) + apps_to_avoid = tuple(options['avoid_apps'].split(",")) + run_server = not options['no_server'] + test_database = options['test_database'] + smtp_queue = options['smtp_queue'] + tags = options['tags'] + failfast = options['failfast'] + auto_pdb = options['auto_pdb'] + threading = options['use_threading'] if test_database: migrate_south = getattr(settings, "SOUTH_TESTS_MIGRATE", True) @@ -184,7 +174,7 @@ def handle(self, *args, **options): self._testrunner.setup_test_environment() self._old_db_config = self._testrunner.setup_databases() - if StrictVersion(django.get_version()) < StrictVersion('1.7'): + if DJANGO_VERSION < StrictVersion('1.7'): call_command('syncdb', verbosity=0, interactive=False,) if migrate_south: call_command('migrate', verbosity=0, interactive=False,) @@ -200,7 +190,7 @@ def handle(self, *args, **options): try: server.start() except LettuceServerException as e: - raise SystemExit(e) + raise CommandError("Couldn't start Django server: %s" % e) os.environ['SERVER_NAME'] = str(server.address) os.environ['SERVER_PORT'] = str(server.port) @@ -237,12 +227,11 @@ def handle(self, *args, **options): results.append(result) if not result or result.steps != result.steps_passed: failed = True - except SystemExit as e: - failed = e.code + except LettuceRunnerError: + failed = True except Exception as e: failed = True - import traceback traceback.print_exc(e) finally: @@ -256,4 +245,5 @@ def handle(self, *args, **options): teardown_test_environment() server.stop(failed) - raise SystemExit(int(failed)) + if failed: + raise CommandError("Lettuce tests failed.") diff --git a/lettuce/django/steps/models.py b/lettuce/django/steps/models.py index 75c410a05..e34e3b7f5 100644 --- a/lettuce/django/steps/models.py +++ b/lettuce/django/steps/models.py @@ -8,13 +8,11 @@ from django.core.management import call_command from django.core.management.color import no_style from django.db import connection - try: + from django.db.models.loading import get_models +except ImportError: from django.apps import apps get_models = apps.get_models -except ImportError: - from django.db.models.loading import get_models - from django.utils.functional import curry from functools import wraps diff --git a/lettuce/exceptions.py b/lettuce/exceptions.py index 27436c859..97b910e45 100644 --- a/lettuce/exceptions.py +++ b/lettuce/exceptions.py @@ -63,3 +63,7 @@ def __init__(self, filename, string): class StepLoadingError(Exception): """Raised when a step cannot be loaded.""" pass + + +class LettuceRunnerError(Exception): + """Raised when the Lettuce runner experiences failures/errors.""" From ce4be04292f2187ae0b1c98211ca7c26dfb9af21 Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Wed, 14 Dec 2016 16:19:08 +0000 Subject: [PATCH 5/9] changed port address to be a string range instead of a fixed int changed port address to be a string range instead of a fixed int also added a reference to the server to lettuce.world --- lettuce/django/server.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lettuce/django/server.py b/lettuce/django/server.py index 18c98313a..923974e1f 100644 --- a/lettuce/django/server.py +++ b/lettuce/django/server.py @@ -30,6 +30,7 @@ from django.core.servers.basehttp import WSGIServer from django.core.servers.basehttp import ServerHandler from django.core.servers.basehttp import WSGIRequestHandler +from lettuce import world try: from django.core.servers.basehttp import AdminMediaHandler @@ -245,9 +246,10 @@ class BaseServer(object): """ def __init__(self, address='0.0.0.0', port=None, threading=True): - self.port = int(port or getattr(settings, 'LETTUCE_SERVER_PORT', 8000)) + self.port = port or getattr(settings, 'LETTUCE_SERVER_PORT', '8081-8179') self.address = unicode(address) self.threading = threading + world.server = self def start(self): """ From 7f9b7ad4d18b20d9f87767ff314555b887e12eac Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Wed, 14 Dec 2016 16:21:03 +0000 Subject: [PATCH 6/9] added code to get actual server port for LiveServerTestCase --- lettuce/django/server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lettuce/django/server.py b/lettuce/django/server.py index 923974e1f..019128d77 100644 --- a/lettuce/django/server.py +++ b/lettuce/django/server.py @@ -350,6 +350,9 @@ def start(self): '{address}:{port}'.format(address=self.address, port=self.port) LiveServerTestCase.setUpClass() + + # now get the actual port that the test case was allocated + self.port = LiveServerTestCase.server_thread.port print "Django's builtin server is running at {address}:{port}".format( address=self.address, From ed1d182b72376b59ecce7c62b78648ed806c2a58 Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Thu, 15 Dec 2016 10:29:10 +0000 Subject: [PATCH 7/9] added django_url setting to world for access in steps --- lettuce/django/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lettuce/django/server.py b/lettuce/django/server.py index 019128d77..e20494b88 100644 --- a/lettuce/django/server.py +++ b/lettuce/django/server.py @@ -353,6 +353,7 @@ def start(self): # now get the actual port that the test case was allocated self.port = LiveServerTestCase.server_thread.port + world.django_url = self.url print "Django's builtin server is running at {address}:{port}".format( address=self.address, From 6412a69d9f200aff81f0172f9cfd9dd0624d93d6 Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Fri, 12 May 2017 15:12:31 +0100 Subject: [PATCH 8/9] added django 1.11 tweaks --- lettuce/django/management/commands/harvest.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lettuce/django/management/commands/harvest.py b/lettuce/django/management/commands/harvest.py index c224f1fec..1ad88278c 100644 --- a/lettuce/django/management/commands/harvest.py +++ b/lettuce/django/management/commands/harvest.py @@ -171,6 +171,10 @@ def handle(self, *args, **options): from django.test.utils import get_runner self._testrunner = get_runner(settings)(interactive=False) + + if DJANGO_VERSION > StrictVersion('1.10'): + self._testrunner.teardown_test_environment() + self._testrunner.setup_test_environment() self._old_db_config = self._testrunner.setup_databases() From a7a2c663ccc22ae6a982e9dc561425b9ce091801 Mon Sep 17 00:00:00 2001 From: Mike Hibbert Date: Thu, 18 May 2017 09:24:03 +0100 Subject: [PATCH 9/9] Update harvest.py --- lettuce/django/management/commands/harvest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lettuce/django/management/commands/harvest.py b/lettuce/django/management/commands/harvest.py index 1ad88278c..4f6d26c6b 100644 --- a/lettuce/django/management/commands/harvest.py +++ b/lettuce/django/management/commands/harvest.py @@ -38,7 +38,7 @@ class Command(BaseCommand): - help = u'Run lettuce tests all along installed apps' + help = 'Run lettuce tests all along installed apps' args = '[PATH to feature file or folder]' if DJANGO_VERSION < StrictVersion('1.7'):