pytest: "unrecognized arguments" or "option names already added" error when running pytest inside a Docker container using Jenkins
This is a complicated set up, so bear with me.
I am trying to create a Jenkins pipeline job to run Pytest on Jenkins inside a Docker container. As far as I can tell, when Jenkins starts a Docker container, it does so with the working directory of that container set to a directory on the Jenkins filesystem, under /var/jenkins_home/<job name>/ by default. Thus, running the tests requires:
- Pull the Docker image, if necessary.
- Create the container using the image. (This gives me access to all the test dependencies, which are already installed inside the image.)
- Checkout the current source code for the tests from GIthub.
- Run pytest.
- Have Jenkins process the resulting jUnit XML.
Because Jenkins passes job parameters as environment variables and some other probably-not-relevant reasons, I need to use a front-end script, run_tests.py, to turn those variables into command-line arguments that pytest can understand. run_tests.py then calls pytest.main().
pytest.main(pytest_args + test_args)
Here, pytest_args are arguments for pytest itself like -s
, -k
, or --tb=<type>
. test_args are command-line arguments that I have configured in conftest.py using pytest_addoption() : these are the arguments created from environment variables. The layout of the project looks like:
repo_root/
repo_root/test_automation/
repo_root/test_automation/conftest.py
repo_root/test_automation/run_tests.py
repo_root/test_automation/test_files.py
This setup works outside Jenkins: I cd to test_automation/, run run_tests.py, it finds conftest.py, and everything is happy. When I run the Jenkins job that’s supposedly executing this same steps with PYTEST_DEBUG set:
12:30:51 pytest_load_initial_conftests [hook]
12:30:51 early_config: <_pytest.config.Config object at 0x7f8e2f160c88>
12:30:51 args: [...]
12:30:51 parser: <_pytest.config.Parser object at 0x7f8e2f1717f0>
12:30:51 pytest_plugin_registered [hook]
12:30:51 plugin: <_pytest.capture.CaptureManager object at 0x7f8e2d6b2438>
12:30:51 manager: <_pytest.config.PytestPluginManager object at 0x7f8e2f5a5d30>
12:30:51 finish pytest_plugin_registered --> [] [hook]
12:30:51 finish pytest_load_initial_conftests --> [] [hook]
12:30:51 pytest_cmdline_preparse [hook]
12:30:51 config: <_pytest.config.Config object at 0x7f8e2f160c88>
12:30:51 args: [...]
12:30:51 finish pytest_cmdline_preparse --> [] [hook]
12:30:51 usage: run_tests.py [options] [file_or_dir] [file_or_dir] [...]
12:30:51 run_tests.py: error: unrecognized arguments: ...
Here, the “…” for the args stands for all the arguments in the first two cases, including the pytest-specific ones, and only the arguments configured in conftest.py for the unrecognized arguments, the last case. From looking at the logs for this same configuration that works outside Jenkins, I know that the pytest_load_initial_conftests log should look like:
pytest_load_initial_conftests [hook]
early_config: <_pytest.config.Config object at 0x10f512208>
args: [...]
parser: <_pytest.config.Parser object at 0x10f512d30>
pytest_plugin_registered [hook]
plugin: <_pytest.capture.CaptureManager object at 0x1101ec5f8>
manager: <_pytest.config.PytestPluginManager object at 0x10f2caba8>
finish pytest_plugin_registered --> [] [hook]
find_module called for: conftest [assertion]
rewriting conftest file: '/Users/user/test_automation/test_automation/conftest.py' [assertion]
found cached rewritten pyc for '/Users/user/test_automation/test_automation/conftest.py' [assertion]
find_module called for: run_tests [assertion]
loaded conftestmodule <module 'conftest' (<_pytest.assertion.rewrite.AssertionRewritingHook object at 0x11019c198>)> [pluginmanage]
pytest_addoption [hook]
parser: <_pytest.config.Parser object at 0x10f512d30>
__multicall__: <_MultiCall 0 results, 0 meths, kwargs={'parser': <_pytest.config.Parser object at 0x10f512d30>, '__multicall__': <_MultiCall 0 results, 0 meths, kwargs={...}>}>
finish pytest_addoption --> [] [hook]
pytest_plugin_registered [hook]
plugin: <module 'conftest' (<_pytest.assertion.rewrite.AssertionRewritingHook object at 0x11019c198>)>
manager: <_pytest.config.PytestPluginManager object at 0x10f2caba8>
finish pytest_plugin_registered --> [] [hook]
finish pytest_load_initial_conftests --> [] [hook]
It looks a lot like pytest is not finding my conftest.py file, even though it’s in the root directory (with respect to how pytest is being run). However, if I have Jenkins run python3 -m pytest --help
in the same directory, with everything else the same, pytest will correctly process conftest.py and display the options that are configured there under the custom options:
heading. I also tried manually forcing pytest to load conftest.py using -p
. When I do that, pytest crashes with a traceback that suggests that conftest.py is already loaded:
14:24:15 pytest_load_initial_conftests [hook]
14:24:15 early_config: <_pytest.config.Config object at 0x7f5acc971780>
14:24:15 args: [...]
14:24:15 parser: <_pytest.config.Parser object at 0x7f5acc9802e8>
14:24:15 pytest_plugin_registered [hook]
14:24:15 plugin: <_pytest.capture.CaptureManager object at 0x7f5acab0ac50>
14:24:15 manager: <_pytest.config.PytestPluginManager object at 0x7f5acc5ff4e0>
14:24:15 finish pytest_plugin_registered --> [] [hook]
14:24:15 find_module called for: conftest [assertion]
14:24:15 rewriting conftest file: '/var/jenkins_home/workspace/Test Automation-pipeline/test_automation/conftest.py' [assertion]
14:24:15 found cached rewritten pyc for '/var/jenkins_home/workspace/Test Automation-pipeline/test_automation/conftest.py' [assertion]
14:24:15 loaded conftestmodule <module 'conftest' (<_pytest.assertion.rewrite.AssertionRewritingHook object at 0x7f5acabb7668>)> [pluginmanage]
14:24:15 pytest_addoption [hook]
14:24:15 parser: <_pytest.config.Parser object at 0x7f5acc9802e8>
14:24:15 __multicall__: <_MultiCall 0 results, 0 meths, kwargs={'parser': <_pytest.config.Parser object at 0x7f5acc9802e8>, '__multicall__': <_MultiCall 0 results, 0 meths, kwargs={...}>}>
14:24:15 Traceback (most recent call last):
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 342, in _getconftestmodules
14:24:15 return self._path2confmods[path]
14:24:15 KeyError: local('/var/jenkins_home/workspace/Test Automation-pipeline/test_automation')
14:24:15
14:24:15 During handling of the above exception, another exception occurred:
14:24:15
14:24:15 Traceback (most recent call last):
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 373, in _importconftest
14:24:15 return self._conftestpath2mod[conftestpath]
14:24:15 KeyError: local('/var/jenkins_home/workspace/Test Automation-pipeline/test_automation/conftest.py')
14:24:15
14:24:15 During handling of the above exception, another exception occurred:
14:24:15
14:24:15 Traceback (most recent call last):
14:24:15 File "run_tests.py", line 435, in <module>
14:24:15 invoke_pytest(args, pytest_args)
14:24:15 File "run_tests.py", line 201, in invoke_pytest
14:24:15 pytest.main(pytest_args + test_args)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 49, in main
14:24:15 config = _prepareconfig(args, plugins)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 168, in _prepareconfig
14:24:15 pluginmanager=pluginmanager, args=args)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 745, in __call__
14:24:15 return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 339, in _hookexec
14:24:15 return self._inner_hookexec(hook, methods, kwargs)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 302, in __call__
14:24:15 return outcome.get_result()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 279, in get_result
14:24:15 raise ex[1].with_traceback(ex[2])
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 265, in __init__
14:24:15 self.result = func()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 300, in <lambda>
14:24:15 outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 334, in <lambda>
14:24:15 _MultiCall(methods, kwargs, hook.spec_opts).execute()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 613, in execute
14:24:15 return _wrapped_call(hook_impl.function(*args), self.execute)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 250, in _wrapped_call
14:24:15 wrap_controller.send(call_outcome)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/helpconfig.py", line 68, in pytest_cmdline_parse
14:24:15 config = outcome.get_result()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 279, in get_result
14:24:15 raise ex[1].with_traceback(ex[2])
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 265, in __init__
14:24:15 self.result = func()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 614, in execute
14:24:15 res = hook_impl.function(*args)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 945, in pytest_cmdline_parse
14:24:15 self.parse(args)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 1116, in parse
14:24:15 self._preparse(args, addopts=addopts)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 1087, in _preparse
14:24:15 args=args, parser=self._parser)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 745, in __call__
14:24:15 return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 339, in _hookexec
14:24:15 return self._inner_hookexec(hook, methods, kwargs)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 302, in __call__
14:24:15 return outcome.get_result()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 279, in get_result
14:24:15 raise ex[1].with_traceback(ex[2])
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 265, in __init__
14:24:15 self.result = func()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 300, in <lambda>
14:24:15 outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 334, in <lambda>
14:24:15 _MultiCall(methods, kwargs, hook.spec_opts).execute()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 613, in execute
14:24:15 return _wrapped_call(hook_impl.function(*args), self.execute)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 254, in _wrapped_call
14:24:15 return call_outcome.get_result()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 279, in get_result
14:24:15 raise ex[1].with_traceback(ex[2])
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 265, in __init__
14:24:15 self.result = func()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 614, in execute
14:24:15 res = hook_impl.function(*args)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 991, in pytest_load_initial_conftests
14:24:15 self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 328, in _set_initial_conftests
14:24:15 self._try_load_conftest(current)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 331, in _try_load_conftest
14:24:15 self._getconftestmodules(anchor)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 356, in _getconftestmodules
14:24:15 mod = self._importconftest(conftestpath)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 392, in _importconftest
14:24:15 self.consider_conftest(mod)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 415, in consider_conftest
14:24:15 self.register(conftestmodule, name=conftestmodule.__file__)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 264, in register
14:24:15 ret = super(PytestPluginManager, self).register(plugin, name)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 371, in register
14:24:15 hook._maybe_apply_history(hookimpl)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 768, in _maybe_apply_history
14:24:15 res = self._hookexec(self, [method], kwargs)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 339, in _hookexec
14:24:15 return self._inner_hookexec(hook, methods, kwargs)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 302, in __call__
14:24:15 return outcome.get_result()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 279, in get_result
14:24:15 raise ex[1].with_traceback(ex[2])
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 265, in __init__
14:24:15 self.result = func()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 300, in <lambda>
14:24:15 outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 334, in <lambda>
14:24:15 _MultiCall(methods, kwargs, hook.spec_opts).execute()
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/vendored_packages/pluggy.py", line 614, in execute
14:24:15 res = hook_impl.function(*args)
14:24:15 File "/var/jenkins_home/workspace/Test Automation-pipeline/test_automation/conftest.py", line 19, in pytest_addoption
14:24:15 parser.addoption(option, *run_tests.TEST_OPTIONS[option][0], **run_tests.TEST_OPTIONS[option][1])
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 537, in addoption
14:24:15 self._anonymous.addoption(*opts, **attrs)
14:24:15 File "/usr/local/lib/python3.6/site-packages/_pytest/config.py", line 762, in addoption
14:24:15 raise ValueError("option names %s already added" % conflict)
14:24:15 ValueError: option names {'--<option'} already added
I tried passing the --debug option to pytest, but it also crashed with this traceback, so it didn’t give me any more information.
- pip list from inside the container:
boltons (17.2.0)
cachetools (2.0.1)
certifi (2017.11.5)
chardet (3.0.4)
ConfigArgParse (0.12.0)
decorator (4.1.2)
dictdiffer (0.7.0)
docker (2.7.0)
docker-pycreds (0.2.1)
dpath (1.4.0)
google-auth (1.2.1)
idna (2.6)
ipaddress (1.0.19)
Jinja2 (2.10)
jsonschema (2.6.0)
kubernetes (3.0.0)
MarkupSafe (1.0)
networkx (2.0)
openshift (0.3.4)
pip (9.0.1)
plumbum (1.6.5)
py (1.5.2)
pyasn1 (0.4.2)
pyasn1-modules (0.2.1)
pytest (3.2.0)
pytest-html (1.16.1)
pytest-metadata (1.5.1)
python-dateutil (2.6.1)
python-string-utils (0.6.0)
PyYAML (3.12)
requests (2.18.4)
rsa (3.4.2)
ruamel.yaml (0.15.35)
setuptools (36.6.0)
six (1.11.0)
swagger-parser (1.0.0)
swagger-spec-validator (2.1.0)
urllib3 (1.22)
websocket-client (0.40.0)
wheel (0.30.0)
-
I’m using Docker for Mac to run Docker on Mac OS X 10.12.6. (Docker for Mac uses Virtualbox to create a VM on Mac OS X.) Docker is version Version 17.09.1-ce-mac42 (21090).
-
Jenkins is also running in a Docker container, and from what I see the Docker socket is mounted inside Jenkins, so there’s only one Docker instance running. Jenkins is version 2.89.2.
Is there anything else I can do to figure out what’s going on here? I haven’t been able to replicate the bug anywhere outside a Jenkins job. Even when I have Jenkins start the container, then use docker exec to log into, I can still run run_tests.py and it doesn’t complain about unrecognized arguments.
About this issue
- Original URL
- State: open
- Created 6 years ago
- Comments: 18 (17 by maintainers)
I think I understand the issue now. I think it might be worth discussing whether there’s a more transparent way to handle command-line options for pytest, like maybe splitting out command-line options into their own file that’s loaded eagerly? That said, this is also a documentation problem, because the existing documentation (https://docs.pytest.org/en/latest/example/simple.html, https://docs.pytest.org/en/latest/example/parametrize.html, https://docs.pytest.org/en/latest/example/markers.html) uses
pytest_addoption
inconftest.py
without mentioning that passing a path in any configured options can cause discovery to go haywire. Thoughts?