From 5c936915b3964e7f71c568219693e43f319b50ca Mon Sep 17 00:00:00 2001
From: John Whitlock <John-Whitlock@ieee.org>
Date: Wed, 8 Apr 2015 17:19:43 -0500
Subject: [PATCH] Convert nose optparse options to argparse
When django.core.management.base.BaseCommand includes 'use_argparse',
then nose's optparse options are merged using argparse's
parser.add_argument in BaseCommand's overriden add_arguments method.
For Django 1.7 and earlier, the current .options method is used to set
the options.
Fixes #178.
---
django_nose/runner.py | 134 +++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 126 insertions(+), 8 deletions(-)
diff --git a/django_nose/runner.py b/django_nose/runner.py
index b99d7fb..b30fdb3 100644
--- a/django_nose/runner.py
+++ b/django_nose/runner.py
@@ -143,7 +143,132 @@ def _get_options():
o.action != 'help')
-class BasicNoseRunner(DiscoverRunner):
+if hasattr(BaseCommand, 'use_argparse'):
+ # Django 1.8 and later uses argparse.ArgumentParser
+ # Translate nose optparse arguments to argparse
+ class BaseRunner(DiscoverRunner):
+
+ # Don't pass the following options to nosetests
+ django_opts = [
+ '--noinput', '--liveserver', '-p', '--pattern', '--testrunner',
+ '--settings']
+
+ #
+ # For optparse -> argparse conversion
+ #
+ # Option strings to remove from Django options if found
+ _argparse_remove_options = (
+ '-p', # Short arg for nose's --plugins, not Django's --patterns
+ '-d', # Short arg for nose's --detailed-errors, not Django's
+ # --debug-sql
+ )
+
+ # Convert nose optparse options to argparse options
+ _argparse_type = {
+ 'int': int,
+ 'float': float,
+ 'complex': complex,
+ 'string': str,
+ }
+ # If optparse has a None argument, omit from call to add_argument
+ _argparse_omit_if_none = (
+ 'action', 'nargs', 'const', 'default', 'type', 'choices',
+ 'required', 'help', 'metavar', 'dest', 'callback', 'callback_args',
+ 'callback_kwargs')
+
+ # Translating callbacks is not supported, because none of the built-in
+ # plugins uses one. If you have a plugin that uses a callback, please
+ # open a ticket or submit a working implementation.
+ _argparse_fail_if_not_none = (
+ 'callback', 'callback_args', 'callback_kwargs')
+
+ @classmethod
+ def add_arguments(cls, parser):
+ """Convert nose's optparse arguments to argparse"""
+ super(BaseRunner, cls).add_arguments(parser)
+
+ # Read optparse options for nose and plugins
+ cfg_files = nose.core.all_config_files()
+ manager = nose.core.DefaultPluginManager()
+ config = nose.core.Config(
+ env=os.environ, files=cfg_files, plugins=manager)
+ config.plugins.addPlugins(list(_get_plugins_from_settings()))
+ options = config.getParser()._get_all_options()
+
+ # Gather existing option strings`
+ django_options = set()
+ for action in parser._actions:
+ for override in cls._argparse_remove_options:
+ if override in action.option_strings:
+ # Emulate parser.conflict_handler='resolve'
+ parser._handle_conflict_resolve(
+ None, ((override, action),))
+ django_options.update(action.option_strings)
+
+ # Process nose optparse options
+ for option in options:
+ # Skip any options also in Django options
+ opt_long = option.get_opt_string()
+ if opt_long in django_options:
+ continue
+ if option._short_opts:
+ opt_short = option._short_opts[0]
+ if opt_short in django_options:
+ continue
+ else:
+ opt_short = None
+
+ # Rename nose's --verbosity to --nose-verbosity
+ if opt_long == '--verbosity':
+ opt_long = '--nose-verbosity'
+
+ # Convert optparse attributes to argparse attributes
+ option_attrs = {}
+ for attr in option.ATTRS:
+ value = getattr(option, attr)
+
+ # Rename options for nose's --verbosity
+ if opt_long == '--nose-verbosity':
+ if attr == 'dest':
+ value = 'nose_verbosity'
+ elif attr == 'metavar':
+ value = 'NOSE_VERBOSITY'
+
+ # Omit arguments that are None, use default
+ if attr in cls._argparse_omit_if_none and value is None:
+ continue
+
+ # Translating callbacks is not supported
+ if attr in cls._argparse_fail_if_not_none:
+ assert value is None, (
+ 'argparse option %s=%s is not supported' %
+ (attr, value))
+ continue
+
+ # Convert type from optparse string to argparse type
+ if attr == 'type':
+ value = cls._argparse_type[value]
+
+ # Pass converted attribute to optparse option
+ option_attrs[attr] = value
+
+ # Add the optparse argument
+ if opt_short:
+ parser.add_argument(opt_short, opt_long, **option_attrs)
+ else:
+ parser.add_argument(opt_long, **option_attrs)
+else:
+ # Django 1.7 and earlier use optparse
+ class BaseRunner(DiscoverRunner):
+ # Replace the builtin options with the merged django/nose options:
+ options = _get_options()
+
+ # Not add following options to nosetests
+ django_opts = ['--noinput', '--liveserver', '-p', '--pattern',
+ '--testrunner']
+
+
+class BasicNoseRunner(BaseRunner):
"""Facade that implements a nose runner in the guise of a Django runner
You shouldn't have to use this directly unless the additions made by
@@ -153,13 +278,6 @@ class BasicNoseRunner(DiscoverRunner):
"""
__test__ = False
- # Replace the builtin command options with the merged django/nose options:
- options = _get_options()
-
- # Not add following options to nosetests
- django_opts = ['--noinput', '--liveserver', '-p', '--pattern',
- '--testrunner']
-
def run_suite(self, nose_argv):
result_plugin = ResultPlugin()
plugins_to_add = [DjangoSetUpPlugin(self),