Merged in issue616 (pull request #252)
fix issue616 - conftest visibility fixes.
This commit is contained in:
commit
1e6e373913
14
CHANGELOG
14
CHANGELOG
|
@ -1,6 +1,20 @@
|
||||||
2.7.0.dev (compared to 2.6.4)
|
2.7.0.dev (compared to 2.6.4)
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
- fix issue616: conftest.py files and their contained fixutres are now
|
||||||
|
properly considered for visibility, independently from the exact
|
||||||
|
current working directory and test arguments that are used.
|
||||||
|
Many thanks to Eric Siegerman and his PR235 which contains
|
||||||
|
systematic tests for conftest visibility and now passes.
|
||||||
|
This change also introduces the concept of a ``rootdir`` which
|
||||||
|
is printed as a new pytest header and documented in the pytest
|
||||||
|
customize web page.
|
||||||
|
|
||||||
|
- change reporting of "diverted" tests, i.e. tests that are collected
|
||||||
|
in one file but actually come from another (e.g. when tests in a test class
|
||||||
|
come from a base class in a different file). We now show the nodeid
|
||||||
|
and indicate via a postfix the other file.
|
||||||
|
|
||||||
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
|
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
|
||||||
|
|
||||||
- added documentation on the new pytest-dev teams on bitbucket and
|
- added documentation on the new pytest-dev teams on bitbucket and
|
||||||
|
|
|
@ -657,6 +657,12 @@ class Config(object):
|
||||||
sys.stderr.write("INTERNALERROR> %s\n" %line)
|
sys.stderr.write("INTERNALERROR> %s\n" %line)
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def cwd_relative_nodeid(self, nodeid):
|
||||||
|
# nodeid's are relative to the rootpath, compute relative to cwd
|
||||||
|
if self.invocation_dir != self.rootdir:
|
||||||
|
fullpath = self.rootdir.join(nodeid)
|
||||||
|
nodeid = self.invocation_dir.bestrelpath(fullpath)
|
||||||
|
return nodeid
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fromdictargs(cls, option_dict, args):
|
def fromdictargs(cls, option_dict, args):
|
||||||
|
@ -691,14 +697,9 @@ class Config(object):
|
||||||
|
|
||||||
def _initini(self, args):
|
def _initini(self, args):
|
||||||
parsed_args = self._parser.parse_known_args(args)
|
parsed_args = self._parser.parse_known_args(args)
|
||||||
if parsed_args.inifilename:
|
r = determine_setup(parsed_args.inifilename, parsed_args.file_or_dir)
|
||||||
iniconfig = py.iniconfig.IniConfig(parsed_args.inifilename)
|
self.rootdir, self.inifile, self.inicfg = r
|
||||||
if 'pytest' in iniconfig.sections:
|
self.invocation_dir = py.path.local()
|
||||||
self.inicfg = iniconfig['pytest']
|
|
||||||
else:
|
|
||||||
self.inicfg = {}
|
|
||||||
else:
|
|
||||||
self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"])
|
|
||||||
self._parser.addini('addopts', 'extra command line options', 'args')
|
self._parser.addini('addopts', 'extra command line options', 'args')
|
||||||
self._parser.addini('minversion', 'minimally required pytest version')
|
self._parser.addini('minversion', 'minimally required pytest version')
|
||||||
|
|
||||||
|
@ -859,8 +860,58 @@ def getcfg(args, inibasenames):
|
||||||
if exists(p):
|
if exists(p):
|
||||||
iniconfig = py.iniconfig.IniConfig(p)
|
iniconfig = py.iniconfig.IniConfig(p)
|
||||||
if 'pytest' in iniconfig.sections:
|
if 'pytest' in iniconfig.sections:
|
||||||
return iniconfig['pytest']
|
return base, p, iniconfig['pytest']
|
||||||
return {}
|
elif inibasename == "pytest.ini":
|
||||||
|
# allowed to be empty
|
||||||
|
return base, p, {}
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_common_ancestor(args):
|
||||||
|
# args are what we get after early command line parsing (usually
|
||||||
|
# strings, but can be py.path.local objects as well)
|
||||||
|
common_ancestor = None
|
||||||
|
for arg in args:
|
||||||
|
if str(arg)[0] == "-":
|
||||||
|
continue
|
||||||
|
p = py.path.local(arg)
|
||||||
|
if common_ancestor is None:
|
||||||
|
common_ancestor = p
|
||||||
|
else:
|
||||||
|
if p.relto(common_ancestor) or p == common_ancestor:
|
||||||
|
continue
|
||||||
|
elif common_ancestor.relto(p):
|
||||||
|
common_ancestor = p
|
||||||
|
else:
|
||||||
|
shared = p.common(common_ancestor)
|
||||||
|
if shared is not None:
|
||||||
|
common_ancestor = shared
|
||||||
|
if common_ancestor is None:
|
||||||
|
common_ancestor = py.path.local()
|
||||||
|
elif not common_ancestor.isdir():
|
||||||
|
common_ancestor = common_ancestor.dirpath()
|
||||||
|
return common_ancestor
|
||||||
|
|
||||||
|
|
||||||
|
def determine_setup(inifile, args):
|
||||||
|
if inifile:
|
||||||
|
iniconfig = py.iniconfig.IniConfig(inifile)
|
||||||
|
try:
|
||||||
|
inicfg = iniconfig["pytest"]
|
||||||
|
except KeyError:
|
||||||
|
inicfg = None
|
||||||
|
rootdir = get_common_ancestor(args)
|
||||||
|
else:
|
||||||
|
ancestor = get_common_ancestor(args)
|
||||||
|
rootdir, inifile, inicfg = getcfg(
|
||||||
|
[ancestor], ["pytest.ini", "tox.ini", "setup.cfg"])
|
||||||
|
if rootdir is None:
|
||||||
|
for rootdir in ancestor.parts(reverse=True):
|
||||||
|
if rootdir.join("setup.py").exists():
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
rootdir = ancestor
|
||||||
|
return rootdir, inifile, inicfg or {}
|
||||||
|
|
||||||
|
|
||||||
def setns(obj, dic):
|
def setns(obj, dic):
|
||||||
|
|
|
@ -457,9 +457,7 @@ class FSCollector(Collector):
|
||||||
self.fspath = fspath
|
self.fspath = fspath
|
||||||
|
|
||||||
def _makeid(self):
|
def _makeid(self):
|
||||||
if self == self.session:
|
relpath = self.fspath.relto(self.config.rootdir)
|
||||||
return "."
|
|
||||||
relpath = self.session.fspath.bestrelpath(self.fspath)
|
|
||||||
if os.sep != "/":
|
if os.sep != "/":
|
||||||
relpath = relpath.replace(os.sep, "/")
|
relpath = relpath.replace(os.sep, "/")
|
||||||
return relpath
|
return relpath
|
||||||
|
@ -510,7 +508,7 @@ class Session(FSCollector):
|
||||||
__module__ = 'builtins' # for py3
|
__module__ = 'builtins' # for py3
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
FSCollector.__init__(self, py.path.local(), parent=None,
|
FSCollector.__init__(self, config.rootdir, parent=None,
|
||||||
config=config, session=self)
|
config=config, session=self)
|
||||||
self.config.pluginmanager.register(self, name="session", prepend=True)
|
self.config.pluginmanager.register(self, name="session", prepend=True)
|
||||||
self._testsfailed = 0
|
self._testsfailed = 0
|
||||||
|
@ -520,6 +518,9 @@ class Session(FSCollector):
|
||||||
self.startdir = py.path.local()
|
self.startdir = py.path.local()
|
||||||
self._fs2hookproxy = {}
|
self._fs2hookproxy = {}
|
||||||
|
|
||||||
|
def _makeid(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
def pytest_collectstart(self):
|
def pytest_collectstart(self):
|
||||||
if self.shouldstop:
|
if self.shouldstop:
|
||||||
raise self.Interrupted(self.shouldstop)
|
raise self.Interrupted(self.shouldstop)
|
||||||
|
@ -663,7 +664,7 @@ class Session(FSCollector):
|
||||||
arg = self._tryconvertpyarg(arg)
|
arg = self._tryconvertpyarg(arg)
|
||||||
parts = str(arg).split("::")
|
parts = str(arg).split("::")
|
||||||
relpath = parts[0].replace("/", os.sep)
|
relpath = parts[0].replace("/", os.sep)
|
||||||
path = self.fspath.join(relpath, abs=True)
|
path = self.config.invocation_dir.join(relpath, abs=True)
|
||||||
if not path.check():
|
if not path.check():
|
||||||
if self.config.option.pyargs:
|
if self.config.option.pyargs:
|
||||||
msg = "file or package not found: "
|
msg = "file or package not found: "
|
||||||
|
|
|
@ -306,9 +306,8 @@ class TmpTestdir:
|
||||||
session = Session(config)
|
session = Session(config)
|
||||||
assert '::' not in str(arg)
|
assert '::' not in str(arg)
|
||||||
p = py.path.local(arg)
|
p = py.path.local(arg)
|
||||||
x = session.fspath.bestrelpath(p)
|
|
||||||
config.hook.pytest_sessionstart(session=session)
|
config.hook.pytest_sessionstart(session=session)
|
||||||
res = session.perform_collect([x], genitems=False)[0]
|
res = session.perform_collect([str(p)], genitems=False)[0]
|
||||||
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
|
config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -395,8 +394,7 @@ class TmpTestdir:
|
||||||
def parseconfigure(self, *args):
|
def parseconfigure(self, *args):
|
||||||
config = self.parseconfig(*args)
|
config = self.parseconfig(*args)
|
||||||
config.do_configure()
|
config.do_configure()
|
||||||
self.request.addfinalizer(lambda:
|
self.request.addfinalizer(config.do_unconfigure)
|
||||||
config.do_unconfigure())
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def getitem(self, source, funcname="test_func"):
|
def getitem(self, source, funcname="test_func"):
|
||||||
|
|
|
@ -1653,11 +1653,9 @@ class FixtureManager:
|
||||||
# what fixtures are visible for particular tests (as denoted
|
# what fixtures are visible for particular tests (as denoted
|
||||||
# by their test id)
|
# by their test id)
|
||||||
if p.basename.startswith("conftest.py"):
|
if p.basename.startswith("conftest.py"):
|
||||||
nodeid = self.session.fspath.bestrelpath(p.dirpath())
|
nodeid = p.dirpath().relto(self.config.rootdir)
|
||||||
if p.sep != "/":
|
if p.sep != "/":
|
||||||
nodeid = nodeid.replace(p.sep, "/")
|
nodeid = nodeid.replace(p.sep, "/")
|
||||||
if nodeid == ".":
|
|
||||||
nodeid = ""
|
|
||||||
self.parsefactories(plugin, nodeid)
|
self.parsefactories(plugin, nodeid)
|
||||||
self._seenplugins.add(plugin)
|
self._seenplugins.add(plugin)
|
||||||
|
|
||||||
|
|
|
@ -218,14 +218,14 @@ def show_simple(terminalreporter, lines, stat, format):
|
||||||
failed = terminalreporter.stats.get(stat)
|
failed = terminalreporter.stats.get(stat)
|
||||||
if failed:
|
if failed:
|
||||||
for rep in failed:
|
for rep in failed:
|
||||||
pos = rep.nodeid
|
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||||
lines.append(format %(pos, ))
|
lines.append(format %(pos,))
|
||||||
|
|
||||||
def show_xfailed(terminalreporter, lines):
|
def show_xfailed(terminalreporter, lines):
|
||||||
xfailed = terminalreporter.stats.get("xfailed")
|
xfailed = terminalreporter.stats.get("xfailed")
|
||||||
if xfailed:
|
if xfailed:
|
||||||
for rep in xfailed:
|
for rep in xfailed:
|
||||||
pos = rep.nodeid
|
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||||
reason = rep.wasxfail
|
reason = rep.wasxfail
|
||||||
lines.append("XFAIL %s" % (pos,))
|
lines.append("XFAIL %s" % (pos,))
|
||||||
if reason:
|
if reason:
|
||||||
|
@ -235,7 +235,7 @@ def show_xpassed(terminalreporter, lines):
|
||||||
xpassed = terminalreporter.stats.get("xpassed")
|
xpassed = terminalreporter.stats.get("xpassed")
|
||||||
if xpassed:
|
if xpassed:
|
||||||
for rep in xpassed:
|
for rep in xpassed:
|
||||||
pos = rep.nodeid
|
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
|
||||||
reason = rep.wasxfail
|
reason = rep.wasxfail
|
||||||
lines.append("XPASS %s %s" %(pos, reason))
|
lines.append("XPASS %s %s" %(pos, reason))
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ class TerminalReporter:
|
||||||
self._numcollected = 0
|
self._numcollected = 0
|
||||||
|
|
||||||
self.stats = {}
|
self.stats = {}
|
||||||
self.startdir = self.curdir = py.path.local()
|
self.startdir = py.path.local()
|
||||||
if file is None:
|
if file is None:
|
||||||
file = sys.stdout
|
file = sys.stdout
|
||||||
self._tw = self.writer = py.io.TerminalWriter(file)
|
self._tw = self.writer = py.io.TerminalWriter(file)
|
||||||
|
@ -111,12 +111,12 @@ class TerminalReporter:
|
||||||
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
|
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
|
||||||
return char in self.reportchars
|
return char in self.reportchars
|
||||||
|
|
||||||
def write_fspath_result(self, fspath, res):
|
def write_fspath_result(self, nodeid, res):
|
||||||
|
fspath = self.config.rootdir.join(nodeid.split("::")[0])
|
||||||
if fspath != self.currentfspath:
|
if fspath != self.currentfspath:
|
||||||
self.currentfspath = fspath
|
self.currentfspath = fspath
|
||||||
#fspath = self.startdir.bestrelpath(fspath)
|
fspath = self.startdir.bestrelpath(fspath)
|
||||||
self._tw.line()
|
self._tw.line()
|
||||||
#relpath = self.startdir.bestrelpath(fspath)
|
|
||||||
self._tw.write(fspath + " ")
|
self._tw.write(fspath + " ")
|
||||||
self._tw.write(res)
|
self._tw.write(res)
|
||||||
|
|
||||||
|
@ -182,12 +182,12 @@ class TerminalReporter:
|
||||||
def pytest_runtest_logstart(self, nodeid, location):
|
def pytest_runtest_logstart(self, nodeid, location):
|
||||||
# ensure that the path is printed before the
|
# ensure that the path is printed before the
|
||||||
# 1st test of a module starts running
|
# 1st test of a module starts running
|
||||||
fspath = nodeid.split("::")[0]
|
|
||||||
if self.showlongtestinfo:
|
if self.showlongtestinfo:
|
||||||
line = self._locationline(fspath, *location)
|
line = self._locationline(nodeid, *location)
|
||||||
self.write_ensure_prefix(line, "")
|
self.write_ensure_prefix(line, "")
|
||||||
elif self.showfspath:
|
elif self.showfspath:
|
||||||
self.write_fspath_result(fspath, "")
|
fsid = nodeid.split("::")[0]
|
||||||
|
self.write_fspath_result(fsid, "")
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, report):
|
def pytest_runtest_logreport(self, report):
|
||||||
rep = report
|
rep = report
|
||||||
|
@ -200,7 +200,7 @@ class TerminalReporter:
|
||||||
return
|
return
|
||||||
if self.verbosity <= 0:
|
if self.verbosity <= 0:
|
||||||
if not hasattr(rep, 'node') and self.showfspath:
|
if not hasattr(rep, 'node') and self.showfspath:
|
||||||
self.write_fspath_result(rep.fspath, letter)
|
self.write_fspath_result(rep.nodeid, letter)
|
||||||
else:
|
else:
|
||||||
self._tw.write(letter)
|
self._tw.write(letter)
|
||||||
else:
|
else:
|
||||||
|
@ -213,7 +213,7 @@ class TerminalReporter:
|
||||||
markup = {'red':True}
|
markup = {'red':True}
|
||||||
elif rep.skipped:
|
elif rep.skipped:
|
||||||
markup = {'yellow':True}
|
markup = {'yellow':True}
|
||||||
line = self._locationline(str(rep.fspath), *rep.location)
|
line = self._locationline(rep.nodeid, *rep.location)
|
||||||
if not hasattr(rep, 'node'):
|
if not hasattr(rep, 'node'):
|
||||||
self.write_ensure_prefix(line, word, **markup)
|
self.write_ensure_prefix(line, word, **markup)
|
||||||
#self._tw.write(word, **markup)
|
#self._tw.write(word, **markup)
|
||||||
|
@ -237,7 +237,7 @@ class TerminalReporter:
|
||||||
items = [x for x in report.result if isinstance(x, pytest.Item)]
|
items = [x for x in report.result if isinstance(x, pytest.Item)]
|
||||||
self._numcollected += len(items)
|
self._numcollected += len(items)
|
||||||
if self.hasmarkup:
|
if self.hasmarkup:
|
||||||
#self.write_fspath_result(report.fspath, 'E')
|
#self.write_fspath_result(report.nodeid, 'E')
|
||||||
self.report_collect()
|
self.report_collect()
|
||||||
|
|
||||||
def report_collect(self, final=False):
|
def report_collect(self, final=False):
|
||||||
|
@ -288,6 +288,10 @@ class TerminalReporter:
|
||||||
self.write_line(line)
|
self.write_line(line)
|
||||||
|
|
||||||
def pytest_report_header(self, config):
|
def pytest_report_header(self, config):
|
||||||
|
inifile = ""
|
||||||
|
if config.inifile:
|
||||||
|
inifile = config.rootdir.bestrelpath(config.inifile)
|
||||||
|
lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
|
||||||
plugininfo = config.pluginmanager._plugin_distinfo
|
plugininfo = config.pluginmanager._plugin_distinfo
|
||||||
if plugininfo:
|
if plugininfo:
|
||||||
l = []
|
l = []
|
||||||
|
@ -296,7 +300,8 @@ class TerminalReporter:
|
||||||
if name.startswith("pytest-"):
|
if name.startswith("pytest-"):
|
||||||
name = name[7:]
|
name = name[7:]
|
||||||
l.append(name)
|
l.append(name)
|
||||||
return "plugins: %s" % ", ".join(l)
|
lines.append("plugins: %s" % ", ".join(l))
|
||||||
|
return lines
|
||||||
|
|
||||||
def pytest_collection_finish(self, session):
|
def pytest_collection_finish(self, session):
|
||||||
if self.config.option.collectonly:
|
if self.config.option.collectonly:
|
||||||
|
@ -378,19 +383,24 @@ class TerminalReporter:
|
||||||
else:
|
else:
|
||||||
excrepr.reprcrash.toterminal(self._tw)
|
excrepr.reprcrash.toterminal(self._tw)
|
||||||
|
|
||||||
def _locationline(self, collect_fspath, fspath, lineno, domain):
|
def _locationline(self, nodeid, fspath, lineno, domain):
|
||||||
|
def mkrel(nodeid):
|
||||||
|
line = self.config.cwd_relative_nodeid(nodeid)
|
||||||
|
if domain and line.endswith(domain):
|
||||||
|
line = line[:-len(domain)]
|
||||||
|
l = domain.split("[")
|
||||||
|
l[0] = l[0].replace('.', '::') # don't replace '.' in params
|
||||||
|
line += "[".join(l)
|
||||||
|
return line
|
||||||
# collect_fspath comes from testid which has a "/"-normalized path
|
# collect_fspath comes from testid which has a "/"-normalized path
|
||||||
if fspath and fspath.replace("\\", "/") != collect_fspath:
|
|
||||||
fspath = "%s <- %s" % (collect_fspath, fspath)
|
|
||||||
if fspath:
|
if fspath:
|
||||||
line = str(fspath)
|
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
||||||
if domain:
|
if nodeid.split("::")[0] != fspath.replace("\\", "/"):
|
||||||
split = str(domain).split('[')
|
res += " <- " + self.startdir.bestrelpath(fspath)
|
||||||
split[0] = split[0].replace('.', '::') # don't replace '.' in params
|
|
||||||
line += "::" + '['.join(split)
|
|
||||||
else:
|
else:
|
||||||
line = "[location]"
|
res = "[location]"
|
||||||
return line + " "
|
return res + " "
|
||||||
|
|
||||||
def _getfailureheadline(self, rep):
|
def _getfailureheadline(self, rep):
|
||||||
if hasattr(rep, 'location'):
|
if hasattr(rep, 'location'):
|
||||||
|
|
|
@ -12,37 +12,73 @@ configurations files by using the general help option::
|
||||||
This will display command line and configuration file settings
|
This will display command line and configuration file settings
|
||||||
which were registered by installed plugins.
|
which were registered by installed plugins.
|
||||||
|
|
||||||
|
.. _rootdir:
|
||||||
.. _inifiles:
|
.. _inifiles:
|
||||||
|
|
||||||
How test configuration is read from configuration INI-files
|
initialization: determining rootdir and inifile
|
||||||
-------------------------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
``pytest`` searches for the first matching ini-style configuration file
|
.. versionadded:: 2.7
|
||||||
in the directories of command line argument and the directories above.
|
|
||||||
It looks for file basenames in this order::
|
|
||||||
|
|
||||||
|
pytest determines a "rootdir" for each test run which depends on
|
||||||
|
the command line arguments (specified test files, paths) and on
|
||||||
|
the existence of inifiles. The determined rootdir and ini-file are
|
||||||
|
printed as part of the pytest header. The rootdir is used for constructing
|
||||||
|
"nodeids" during collection and may also be used by plugins to store
|
||||||
|
project/testrun-specific information.
|
||||||
|
|
||||||
|
Here is the algorithm which finds the rootdir from ``args``:
|
||||||
|
|
||||||
|
- determine the common ancestor directory for the specified ``args``.
|
||||||
|
|
||||||
|
- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the
|
||||||
|
ancestor directory and upwards. If one is matched, it becomes the
|
||||||
|
ini-file and its directory becomes the rootdir. An existing
|
||||||
|
``pytest.ini`` file will always be considered a match whereas
|
||||||
|
``tox.ini`` and ``setup.cfg`` will only match if they contain
|
||||||
|
a ``[pytest]`` section.
|
||||||
|
|
||||||
|
- if no ini-file was found, look for ``setup.py`` upwards from
|
||||||
|
the common ancestor directory to determine the ``rootdir``.
|
||||||
|
|
||||||
|
- if no ini-file and no ``setup.py`` was found, use the already
|
||||||
|
determined common ancestor as root directory. This allows to
|
||||||
|
work with pytest in structures that are not part of a package
|
||||||
|
and don't have any particular ini-file configuration.
|
||||||
|
|
||||||
|
Note that options from multiple ini-files candidates are never merged,
|
||||||
|
the first one wins (``pytest.ini`` always wins even if it does not
|
||||||
|
contain a ``[pytest]`` section).
|
||||||
|
|
||||||
|
The ``config`` object will subsequently carry these attributes:
|
||||||
|
|
||||||
|
- ``config.rootdir``: the determined root directory, guaranteed to exist.
|
||||||
|
|
||||||
|
- ``config.inifile``: the determined ini-file, may be ``None``.
|
||||||
|
|
||||||
|
The rootdir is used a reference directory for constructing test
|
||||||
|
addresses ("nodeids") and can be used also by plugins for storing
|
||||||
|
per-testrun information.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
py.test path/to/testdir path/other/
|
||||||
|
|
||||||
|
will determine the common ancestor as ``path`` and then
|
||||||
|
check for ini-files as follows::
|
||||||
|
|
||||||
|
# first look for pytest.ini files
|
||||||
|
path/pytest.ini
|
||||||
|
path/setup.cfg # must also contain [pytest] section to match
|
||||||
|
path/tox.ini # must also contain [pytest] section to match
|
||||||
pytest.ini
|
pytest.ini
|
||||||
tox.ini
|
... # all the way down to the root
|
||||||
setup.cfg
|
|
||||||
|
|
||||||
Searching stops when the first ``[pytest]`` section is found in any of
|
# now look for setup.py
|
||||||
these files. There is no merging of configuration values from multiple
|
path/setup.py
|
||||||
files. Example::
|
setup.py
|
||||||
|
... # all the way down to the root
|
||||||
|
|
||||||
py.test path/to/testdir
|
|
||||||
|
|
||||||
will look in the following dirs for a config file::
|
|
||||||
|
|
||||||
path/to/testdir/pytest.ini
|
|
||||||
path/to/testdir/tox.ini
|
|
||||||
path/to/testdir/setup.cfg
|
|
||||||
path/to/pytest.ini
|
|
||||||
path/to/tox.ini
|
|
||||||
path/to/setup.cfg
|
|
||||||
... # up until root of filesystem
|
|
||||||
|
|
||||||
If argument is provided to a ``pytest`` run, the current working directory
|
|
||||||
is used to start the search.
|
|
||||||
|
|
||||||
.. _`how to change command line options defaults`:
|
.. _`how to change command line options defaults`:
|
||||||
.. _`adding default options`:
|
.. _`adding default options`:
|
||||||
|
@ -67,6 +103,8 @@ line options while the environment is in use::
|
||||||
|
|
||||||
From now on, running ``pytest`` will add the specified options.
|
From now on, running ``pytest`` will add the specified options.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Builtin configuration file options
|
Builtin configuration file options
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -343,7 +343,7 @@ class TestSession:
|
||||||
("pytest_make_collect_report", "collector.fspath == p"),
|
("pytest_make_collect_report", "collector.fspath == p"),
|
||||||
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
|
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
|
||||||
("pytest_collectreport", "report.nodeid == '.'")
|
("pytest_collectreport", "report.nodeid == ''")
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_collect_protocol_method(self, testdir):
|
def test_collect_protocol_method(self, testdir):
|
||||||
|
@ -478,7 +478,7 @@ class Test_getinitialnodes:
|
||||||
config = testdir.parseconfigure(x)
|
config = testdir.parseconfigure(x)
|
||||||
col = testdir.getnode(config, x)
|
col = testdir.getnode(config, x)
|
||||||
assert isinstance(col, pytest.Module)
|
assert isinstance(col, pytest.Module)
|
||||||
assert col.name == 'subdir/x.py'
|
assert col.name == 'x.py'
|
||||||
assert col.parent.parent is None
|
assert col.parent.parent is None
|
||||||
for col in col.listchain():
|
for col in col.listchain():
|
||||||
assert col.config is config
|
assert col.config is config
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import py, pytest
|
import py, pytest
|
||||||
|
|
||||||
from _pytest.config import getcfg
|
from _pytest.config import getcfg, get_common_ancestor, determine_setup
|
||||||
|
|
||||||
class TestParseIni:
|
class TestParseIni:
|
||||||
def test_getcfg_and_config(self, testdir, tmpdir):
|
def test_getcfg_and_config(self, testdir, tmpdir):
|
||||||
|
@ -10,7 +10,7 @@ class TestParseIni:
|
||||||
[pytest]
|
[pytest]
|
||||||
name = value
|
name = value
|
||||||
"""))
|
"""))
|
||||||
cfg = getcfg([sub], ["setup.cfg"])
|
rootdir, inifile, cfg = getcfg([sub], ["setup.cfg"])
|
||||||
assert cfg['name'] == "value"
|
assert cfg['name'] == "value"
|
||||||
config = testdir.parseconfigure(sub)
|
config = testdir.parseconfigure(sub)
|
||||||
assert config.inicfg['name'] == 'value'
|
assert config.inicfg['name'] == 'value'
|
||||||
|
@ -400,3 +400,55 @@ class TestWarning:
|
||||||
*WT1*test_warn_on_test_item*:5*hello*
|
*WT1*test_warn_on_test_item*:5*hello*
|
||||||
*1 warning*
|
*1 warning*
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
class TestRootdir:
|
||||||
|
def test_simple_noini(self, tmpdir):
|
||||||
|
assert get_common_ancestor([tmpdir]) == tmpdir
|
||||||
|
assert get_common_ancestor([tmpdir.mkdir("a"), tmpdir]) == tmpdir
|
||||||
|
assert get_common_ancestor([tmpdir, tmpdir.join("a")]) == tmpdir
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
assert get_common_ancestor([]) == tmpdir
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
|
||||||
|
def test_with_ini(self, tmpdir, name):
|
||||||
|
inifile = tmpdir.join(name)
|
||||||
|
inifile.write("[pytest]\n")
|
||||||
|
|
||||||
|
a = tmpdir.mkdir("a")
|
||||||
|
b = a.mkdir("b")
|
||||||
|
for args in ([tmpdir], [a], [b]):
|
||||||
|
rootdir, inifile, inicfg = determine_setup(None, args)
|
||||||
|
assert rootdir == tmpdir
|
||||||
|
assert inifile == inifile
|
||||||
|
rootdir, inifile, inicfg = determine_setup(None, [b,a])
|
||||||
|
assert rootdir == tmpdir
|
||||||
|
assert inifile == inifile
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", "setup.cfg tox.ini".split())
|
||||||
|
def test_pytestini_overides_empty_other(self, tmpdir, name):
|
||||||
|
inifile = tmpdir.ensure("pytest.ini")
|
||||||
|
a = tmpdir.mkdir("a")
|
||||||
|
a.ensure(name)
|
||||||
|
rootdir, inifile, inicfg = determine_setup(None, [a])
|
||||||
|
assert rootdir == tmpdir
|
||||||
|
assert inifile == inifile
|
||||||
|
|
||||||
|
def test_setuppy_fallback(self, tmpdir):
|
||||||
|
a = tmpdir.mkdir("a")
|
||||||
|
a.ensure("setup.cfg")
|
||||||
|
tmpdir.ensure("setup.py")
|
||||||
|
rootdir, inifile, inicfg = determine_setup(None, [a])
|
||||||
|
assert rootdir == tmpdir
|
||||||
|
assert inifile is None
|
||||||
|
assert inicfg == {}
|
||||||
|
|
||||||
|
def test_nothing(self, tmpdir):
|
||||||
|
rootdir, inifile, inicfg = determine_setup(None, [tmpdir])
|
||||||
|
assert rootdir == tmpdir
|
||||||
|
assert inifile is None
|
||||||
|
assert inicfg == {}
|
||||||
|
|
||||||
|
def test_with_specific_inifile(self, tmpdir):
|
||||||
|
inifile = tmpdir.ensure("pytest.ini")
|
||||||
|
rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir])
|
||||||
|
assert rootdir == tmpdir
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
from textwrap import dedent
|
||||||
import py, pytest
|
import py, pytest
|
||||||
from _pytest.config import Conftest
|
from _pytest.config import Conftest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
||||||
def basedir(request):
|
def basedir(request):
|
||||||
from _pytest.tmpdir import tmpdir
|
from _pytest.tmpdir import tmpdir
|
||||||
|
@ -255,3 +257,89 @@ def test_conftest_found_with_double_dash(testdir):
|
||||||
result.stdout.fnmatch_lines("""
|
result.stdout.fnmatch_lines("""
|
||||||
*--hello-world*
|
*--hello-world*
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
class TestConftestVisibility:
|
||||||
|
def _setup_tree(self, testdir): # for issue616
|
||||||
|
# example mostly taken from:
|
||||||
|
# https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html
|
||||||
|
runner = testdir.mkdir("empty")
|
||||||
|
package = testdir.mkdir("package")
|
||||||
|
|
||||||
|
package.join("conftest.py").write(dedent("""\
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture
|
||||||
|
def fxtr():
|
||||||
|
return "from-package"
|
||||||
|
"""))
|
||||||
|
package.join("test_pkgroot.py").write(dedent("""\
|
||||||
|
def test_pkgroot(fxtr):
|
||||||
|
assert fxtr == "from-package"
|
||||||
|
"""))
|
||||||
|
|
||||||
|
swc = package.mkdir("swc")
|
||||||
|
swc.join("__init__.py").ensure()
|
||||||
|
swc.join("conftest.py").write(dedent("""\
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture
|
||||||
|
def fxtr():
|
||||||
|
return "from-swc"
|
||||||
|
"""))
|
||||||
|
swc.join("test_with_conftest.py").write(dedent("""\
|
||||||
|
def test_with_conftest(fxtr):
|
||||||
|
assert fxtr == "from-swc"
|
||||||
|
|
||||||
|
"""))
|
||||||
|
|
||||||
|
snc = package.mkdir("snc")
|
||||||
|
snc.join("__init__.py").ensure()
|
||||||
|
snc.join("test_no_conftest.py").write(dedent("""\
|
||||||
|
def test_no_conftest(fxtr):
|
||||||
|
assert fxtr == "from-package" # No local conftest.py, so should
|
||||||
|
# use value from parent dir's
|
||||||
|
|
||||||
|
"""))
|
||||||
|
print ("created directory structure:")
|
||||||
|
for x in testdir.tmpdir.visit():
|
||||||
|
print (" " + x.relto(testdir.tmpdir))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"runner": runner,
|
||||||
|
"package": package,
|
||||||
|
"swc": swc,
|
||||||
|
"snc": snc}
|
||||||
|
|
||||||
|
# N.B.: "swc" stands for "subdir with conftest.py"
|
||||||
|
# "snc" stands for "subdir no [i.e. without] conftest.py"
|
||||||
|
@pytest.mark.parametrize("chdir,testarg,expect_ntests_passed", [
|
||||||
|
("runner", "..", 3),
|
||||||
|
("package", "..", 3),
|
||||||
|
("swc", "../..", 3),
|
||||||
|
("snc", "../..", 3),
|
||||||
|
|
||||||
|
("runner", "../package", 3),
|
||||||
|
("package", ".", 3),
|
||||||
|
("swc", "..", 3),
|
||||||
|
("snc", "..", 3),
|
||||||
|
|
||||||
|
("runner", "../package/swc", 1),
|
||||||
|
("package", "./swc", 1),
|
||||||
|
("swc", ".", 1),
|
||||||
|
("snc", "../swc", 1),
|
||||||
|
|
||||||
|
("runner", "../package/snc", 1),
|
||||||
|
("package", "./snc", 1),
|
||||||
|
("swc", "../snc", 1),
|
||||||
|
("snc", ".", 1),
|
||||||
|
])
|
||||||
|
@pytest.mark.issue616
|
||||||
|
def test_parsefactories_relative_node_ids(
|
||||||
|
self, testdir, chdir,testarg, expect_ntests_passed):
|
||||||
|
dirs = self._setup_tree(testdir)
|
||||||
|
print("pytest run in cwd: %s" %(
|
||||||
|
dirs[chdir].relto(testdir.tmpdir)))
|
||||||
|
print("pytestarg : %s" %(testarg))
|
||||||
|
print("expected pass : %s" %(expect_ntests_passed))
|
||||||
|
with dirs[chdir].as_cwd():
|
||||||
|
reprec = testdir.inline_run(testarg, "-q", "--traceconfig")
|
||||||
|
reprec.assertoutcome(passed=expect_ntests_passed)
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
|
from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile
|
||||||
import py, pytest
|
import py, pytest
|
||||||
|
|
||||||
import pdb
|
|
||||||
|
|
||||||
|
|
||||||
class TestDoctests:
|
class TestDoctests:
|
||||||
|
|
||||||
def test_collect_testtextfile(self, testdir):
|
def test_collect_testtextfile(self, testdir):
|
||||||
|
|
|
@ -77,11 +77,11 @@ class TestTerminal:
|
||||||
def test_writeline(self, testdir, linecomp):
|
def test_writeline(self, testdir, linecomp):
|
||||||
modcol = testdir.getmodulecol("def test_one(): pass")
|
modcol = testdir.getmodulecol("def test_one(): pass")
|
||||||
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
|
rep = TerminalReporter(modcol.config, file=linecomp.stringio)
|
||||||
rep.write_fspath_result(py.path.local("xy.py"), '.')
|
rep.write_fspath_result(modcol.nodeid, ".")
|
||||||
rep.write_line("hello world")
|
rep.write_line("hello world")
|
||||||
lines = linecomp.stringio.getvalue().split('\n')
|
lines = linecomp.stringio.getvalue().split('\n')
|
||||||
assert not lines[0]
|
assert not lines[0]
|
||||||
assert lines[1].endswith("xy.py .")
|
assert lines[1].endswith(modcol.name + " .")
|
||||||
assert lines[2] == "hello world"
|
assert lines[2] == "hello world"
|
||||||
|
|
||||||
def test_show_runtest_logstart(self, testdir, linecomp):
|
def test_show_runtest_logstart(self, testdir, linecomp):
|
||||||
|
@ -126,7 +126,7 @@ class TestTerminal:
|
||||||
])
|
])
|
||||||
result = testdir.runpytest("-v", p2)
|
result = testdir.runpytest("-v", p2)
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_p2.py <- *test_p1.py::TestMore::test_p1*",
|
"*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir):
|
def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir):
|
||||||
|
|
Loading…
Reference in New Issue