From 616d8251f34bdd9a4fe7e0bac265412ab8cea961 Mon Sep 17 00:00:00 2001 From: Buck Golemon Date: Thu, 24 Sep 2015 14:13:36 -0700 Subject: [PATCH 1/5] unit tests of Config.fromdictargs. currently failing --- testing/test_config.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/testing/test_config.py b/testing/test_config.py index d497200ee..601985e19 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -264,6 +264,51 @@ class TestConfigAPI: assert len(l) == 2 assert l == ["456", "123"] + +class TestConfigFromdictargs: + @pytest.mark.xfail(reason="fromdictargs currently broken #1060") + def test_basic_behavior(self): + from _pytest.config import Config + option_dict = { + 'verbose': 1e100, + 'foo': 'bar', + } + args = ['a', 'b'] + + config = Config.fromdictargs(option_dict, args) + with pytest.raises(AssertionError): + config.parse(['should to parse again']) + assert config.option.verbose == 1e100 + assert config.option.foo == 'bar' + assert config.args == args + + @pytest.mark.xfail(reason="fromdictargs currently broken #1060") + def test_origargs(self): + """Show that fromdictargs can handle args in their "orig" format""" + from _pytest.config import Config + option_dict = {} + args = ['-vvvv', 'a', 'b'] + + config = Config.fromdictargs(option_dict, args) + assert config.args == ['a', 'b'] + assert config._origargs == ['-vvvv', 'a', 'b'] + assert config.option.verbose == 4 + + @pytest.mark.xfail(reason="fromdictargs currently broken #1060") + def test_inifilename(self): + from _pytest.config import Config + inifile = '../../foo/bar.ini', + option_dict = { + 'inifilename': inifile, + } + + config = Config.fromdictargs(option_dict, ()) + assert config.option.inifilename == inifile + + # this indicates this is the file used for getting configuration values + assert config.inifile == inifile + + def test_options_on_small_file_do_not_blow_up(testdir): def runfiletest(opts): reprec = testdir.inline_run(*opts) From 49d46a0059e5a167469a8c702564b41510ed1995 Mon Sep 17 00:00:00 2001 From: Buck Golemon Date: Thu, 24 Sep 2015 16:10:01 -0700 Subject: [PATCH 2/5] an ugly patch to fix all but the most important part =/ --- _pytest/config.py | 6 +++--- testing/test_config.py | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 0495aa21f..525f0fbf2 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -879,7 +879,7 @@ class Config(object): def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ config = get_config() - config._preparse(args, addopts=False) + config.parse(args, addopts=False) config.option.__dict__.update(option_dict) for x in config.option.plugins: config.pluginmanager.consider_pluginarg(x) @@ -947,14 +947,14 @@ class Config(object): self.inicfg.config.path, self.inicfg.lineof('minversion'), minver, pytest.__version__)) - def parse(self, args): + def parse(self, args, addopts=True): # parse given cmdline arguments into this config object. assert not hasattr(self, 'args'), ( "can only parse cmdline args at most once per Config object") self._origargs = args self.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=self.pluginmanager)) - self._preparse(args) + self._preparse(args, addopts=addopts) # XXX deprecated hook: self.hook.pytest_cmdline_preparse(config=self, args=args) args = self._parser.parse_setoption(args, self.option) diff --git a/testing/test_config.py b/testing/test_config.py index 601985e19..981138e75 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -266,7 +266,6 @@ class TestConfigAPI: class TestConfigFromdictargs: - @pytest.mark.xfail(reason="fromdictargs currently broken #1060") def test_basic_behavior(self): from _pytest.config import Config option_dict = { @@ -282,7 +281,6 @@ class TestConfigFromdictargs: assert config.option.foo == 'bar' assert config.args == args - @pytest.mark.xfail(reason="fromdictargs currently broken #1060") def test_origargs(self): """Show that fromdictargs can handle args in their "orig" format""" from _pytest.config import Config From 0e55a8793f182518aef3401b4893fe4caeea962a Mon Sep 17 00:00:00 2001 From: Buck Golemon Date: Thu, 24 Sep 2015 17:52:53 -0700 Subject: [PATCH 3/5] all tests pass --- _pytest/config.py | 30 ++++++++++++++++-------------- testing/test_config.py | 32 +++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 525f0fbf2..5ffed81dd 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -455,11 +455,11 @@ class Parser: """ self._anonymous.addoption(*opts, **attrs) - def parse(self, args): + def parse(self, args, namespace=None): from _pytest._argcomplete import try_argcomplete self.optparser = self._getparser() try_argcomplete(self.optparser) - return self.optparser.parse_args([str(x) for x in args]) + return self.optparser.parse_args([str(x) for x in args], namespace=namespace) def _getparser(self): from _pytest._argcomplete import filescompleter @@ -477,25 +477,25 @@ class Parser: optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter return optparser - def parse_setoption(self, args, option): - parsedoption = self.parse(args) + def parse_setoption(self, args, option, namespace=None): + parsedoption = self.parse(args, namespace=namespace) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) return getattr(parsedoption, FILE_OR_DIR) - def parse_known_args(self, args): + def parse_known_args(self, args, namespace=None): """parses and returns a namespace object with known arguments at this point. """ - return self.parse_known_and_unknown_args(args)[0] + return self.parse_known_and_unknown_args(args, namespace=namespace)[0] - def parse_known_and_unknown_args(self, args): + def parse_known_and_unknown_args(self, args, namespace=None): """parses and returns a namespace object with known arguments, and the remaining arguments unknown at this point. """ optparser = self._getparser() args = [str(x) for x in args] - return optparser.parse_known_args(args) + return optparser.parse_known_args(args, namespace=namespace) def addini(self, name, help, type=None, default=None): """ register an ini-file option. @@ -779,10 +779,12 @@ def _ensure_removed_sysmodule(modname): class CmdOptions(object): """ holds cmdline options as attributes.""" - def __init__(self, **kwargs): - self.__dict__.update(kwargs) + def __init__(self, values=()): + self.__dict__.update(values) def __repr__(self): return "" %(self.__dict__,) + def copy(self): + return CmdOptions(self.__dict__) class Notset: def __repr__(self): @@ -879,8 +881,8 @@ class Config(object): def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ config = get_config() - config.parse(args, addopts=False) config.option.__dict__.update(option_dict) + config.parse(args, addopts=False) for x in config.option.plugins: config.pluginmanager.consider_pluginarg(x) return config @@ -898,7 +900,7 @@ class Config(object): self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) def _initini(self, args): - ns, unknown_args = self._parser.parse_known_and_unknown_args(args) + ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy()) r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args) self.rootdir, self.inifile, self.inicfg = r self._parser.extra_info['rootdir'] = self.rootdir @@ -919,7 +921,7 @@ class Config(object): except ImportError as e: self.warn("I2", "could not load setuptools entry import: %s" % (e,)) self.pluginmanager.consider_env() - self.known_args_namespace = ns = self._parser.parse_known_args(args) + self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) if self.known_args_namespace.confcutdir is None and self.inifile: confcutdir = py.path.local(self.inifile).dirname self.known_args_namespace.confcutdir = confcutdir @@ -957,7 +959,7 @@ class Config(object): self._preparse(args, addopts=addopts) # XXX deprecated hook: self.hook.pytest_cmdline_preparse(config=self, args=args) - args = self._parser.parse_setoption(args, self.option) + args = self._parser.parse_setoption(args, self.option, namespace=self.option) if not args: cwd = os.getcwd() if cwd == self.rootdir: diff --git a/testing/test_config.py b/testing/test_config.py index 981138e75..f8492097c 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -269,42 +269,56 @@ class TestConfigFromdictargs: def test_basic_behavior(self): from _pytest.config import Config option_dict = { - 'verbose': 1e100, + 'verbose': 444, 'foo': 'bar', + 'capture': 'no', } args = ['a', 'b'] config = Config.fromdictargs(option_dict, args) with pytest.raises(AssertionError): - config.parse(['should to parse again']) - assert config.option.verbose == 1e100 + config.parse(['should refuse to parse again']) + assert config.option.verbose == 444 assert config.option.foo == 'bar' + assert config.option.capture == 'no' assert config.args == args def test_origargs(self): """Show that fromdictargs can handle args in their "orig" format""" from _pytest.config import Config option_dict = {} - args = ['-vvvv', 'a', 'b'] + args = ['-vvvv', '-s', 'a', 'b'] config = Config.fromdictargs(option_dict, args) assert config.args == ['a', 'b'] - assert config._origargs == ['-vvvv', 'a', 'b'] + assert config._origargs == args assert config.option.verbose == 4 + assert config.option.capture == 'no' + + def test_inifilename(self, tmpdir): + tmpdir.join("foo/bar.ini").ensure().write(py.code.Source(""" + [pytest] + name = value + """)) - @pytest.mark.xfail(reason="fromdictargs currently broken #1060") - def test_inifilename(self): from _pytest.config import Config - inifile = '../../foo/bar.ini', + inifile = '../../foo/bar.ini' option_dict = { 'inifilename': inifile, + 'capture': 'no', } - config = Config.fromdictargs(option_dict, ()) + cwd = tmpdir.join('a/b') + with cwd.ensure(dir=True).as_cwd(): + config = Config.fromdictargs(option_dict, ()) + + assert config.args == [str(cwd)] assert config.option.inifilename == inifile + assert config.option.capture == 'no' # this indicates this is the file used for getting configuration values assert config.inifile == inifile + assert config.inicfg.get('name') == 'value' def test_options_on_small_file_do_not_blow_up(testdir): From 470e4f9e910837c5fc99b038b7ffcf653cffa061 Mon Sep 17 00:00:00 2001 From: Buck Golemon Date: Thu, 8 Oct 2015 10:51:22 -0700 Subject: [PATCH 4/5] changelog entry --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e20532a9b..530ecf0ea 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,9 @@ * New `pytest.mark.skip` mark, which unconditional skips marked tests. Thanks Michael Aquilina for the complete PR. +* fix issue #680: the -s and -c options should now work under xdist; + `Config.fromdictargs` now represents its input much more faithfully. + Thanks to Buck Evan for the complete PR. 2.8.2.dev --------- From 67236d6de38571f2835c04f00a583aa10658b509 Mon Sep 17 00:00:00 2001 From: Buck Golemon Date: Fri, 9 Oct 2015 09:57:40 -0700 Subject: [PATCH 5/5] strengthen the ini assertion --- testing/test_config.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/testing/test_config.py b/testing/test_config.py index f8492097c..e818dff38 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -309,6 +309,11 @@ class TestConfigFromdictargs: } cwd = tmpdir.join('a/b') + cwd.join('pytest.ini').ensure().write(py.code.Source(""" + [pytest] + name = wrong-value + should_not_be_set = true + """)) with cwd.ensure(dir=True).as_cwd(): config = Config.fromdictargs(option_dict, ()) @@ -319,6 +324,7 @@ class TestConfigFromdictargs: # this indicates this is the file used for getting configuration values assert config.inifile == inifile assert config.inicfg.get('name') == 'value' + assert config.inicfg.get('should_not_be_set') is None def test_options_on_small_file_do_not_blow_up(testdir):