diff --git a/py/impl/test/cmdline.py b/py/impl/test/cmdline.py index 5ee1ce8ce..92aab633e 100644 --- a/py/impl/test/cmdline.py +++ b/py/impl/test/cmdline.py @@ -13,7 +13,8 @@ def main(args=None): config.parse(args) config.pluginmanager.do_configure(config) session = config.initsession() - exitstatus = session.main() + colitems = config.getinitialnodes() + exitstatus = session.main(colitems) config.pluginmanager.do_unconfigure(config) raise SystemExit(exitstatus) except config.Error: diff --git a/py/impl/test/collect.py b/py/impl/test/collect.py index 99cd1c2ff..d547d17e9 100644 --- a/py/impl/test/collect.py +++ b/py/impl/test/collect.py @@ -383,24 +383,10 @@ class RootCollector(Directory): Directory.__init__(self, config.topdir, parent=None, config=config) self.name = None - def getfsnode(self, path): - path = py.path.local(path) - if not path.check(): - raise self.config.Error("file not found: %s" %(path,)) - topdir = self.config.topdir - if path != topdir and not path.relto(topdir): - raise self.config.Error("path %r is not relative to %r" % - (str(path), str(self.fspath))) - # assumtion: pytest's fs-collector tree follows the filesystem tree - basenames = filter(None, path.relto(topdir).split(path.sep)) - try: - return self.getbynames(basenames) - except ValueError: - raise self.config.Error("can't collect: %s" % str(path)) - def getbynames(self, names): current = self.consider(self.config.topdir) - for name in names: + while names: + name = names.pop(0) if name == ".": # special "identity" name continue l = [] @@ -409,8 +395,12 @@ class RootCollector(Directory): l.append(x) elif x.fspath == current.fspath.join(name): l.append(x) + elif x.name == "()": + names.insert(0, name) + l.append(x) + break if not l: - raise ValueError("no node named %r in %r" %(name, current)) + raise ValueError("no node named %r below %r" %(name, current)) current = l[0] return current diff --git a/py/impl/test/config.py b/py/impl/test/config.py index 147afd0c9..ef70a1550 100644 --- a/py/impl/test/config.py +++ b/py/impl/test/config.py @@ -99,7 +99,11 @@ class Config(object): args.append(py.std.os.getcwd()) self.topdir = gettopdir(args) self._rootcol = RootCollector(config=self) - self.args = [py.path.local(x) for x in args] + self._setargs(args) + + def _setargs(self, args): + self.args = list(args) + self._argfspaths = [py.path.local(decodearg(x)[0]) for x in args] # config objects are usually pickled across system # barriers but they contain filesystem paths. @@ -121,10 +125,10 @@ class Config(object): self.__init__(topdir=py.path.local()) self._rootcol = RootCollector(config=self) args, cmdlineopts = repr - args = [self.topdir.join(x) for x in args] + args = [str(self.topdir.join(x)) for x in args] self.option = cmdlineopts self._preparse(args) - self.args = args + self._setargs(args) def ensuretemp(self, string, dir=True): return self.getbasetemp().ensure(string, dir=dir) @@ -149,11 +153,26 @@ class Config(object): return py.path.local.make_numbered_dir(prefix=basename + "-", keep=0, rootdir=basetemp, lock_timeout=None) - def getcolitems(self): - return [self.getfsnode(arg) for arg in self.args] + def getinitialnodes(self): + return [self.getnode(arg) for arg in self.args] - def getfsnode(self, path): - return self._rootcol.getfsnode(path) + def getnode(self, arg): + parts = decodearg(arg) + path = py.path.local(parts.pop(0)) + if not path.check(): + raise self.Error("file not found: %s" %(path,)) + topdir = self.topdir + if path != topdir and not path.relto(topdir): + raise self.Error("path %r is not relative to %r" % + (str(path), str(topdir))) + # assumtion: pytest's fs-collector tree follows the filesystem tree + names = filter(None, path.relto(topdir).split(path.sep)) + names.extend(parts) + try: + return self._rootcol.getbynames(names) + except ValueError: + e = py.std.sys.exc_info()[1] + raise self.Error("can't collect: %s\n%s" % (arg, e.args[0])) def _getcollectclass(self, name, path): try: @@ -282,11 +301,11 @@ def gettopdir(args): if the common base dir resides in a python package parent directory of the root package is returned. """ - args = [py.path.local(arg) for arg in args] - p = args and args[0] or None - for x in args[1:]: + fsargs = [py.path.local(decodearg(arg)[0]) for arg in args] + p = fsargs and fsargs[0] or None + for x in fsargs[1:]: p = p.common(x) - assert p, "cannot determine common basedir of %s" %(args,) + assert p, "cannot determine common basedir of %s" %(fsargs,) pkgdir = p.pypkgpath() if pkgdir is None: if p.check(file=1): @@ -295,6 +314,10 @@ def gettopdir(args): else: return pkgdir.dirpath() +def decodearg(arg): + arg = str(arg) + return arg.split("::") + def onpytestaccess(): # it's enough to have our containing module loaded as # it initializes a per-process config instance diff --git a/py/impl/test/dist/dsession.py b/py/impl/test/dist/dsession.py index 4cc5b2d31..7f6b01d3c 100644 --- a/py/impl/test/dist/dsession.py +++ b/py/impl/test/dist/dsession.py @@ -85,8 +85,7 @@ class DSession(Session): # raise config.Error("dist mode %r needs test execution environments, " # "none found." %(config.option.dist)) - def main(self, colitems=None): - colitems = self.getinitialitems(colitems) + def main(self, colitems): self.sessionstarts() self.setup() exitstatus = self.loop(colitems) diff --git a/py/impl/test/looponfail/remote.py b/py/impl/test/looponfail/remote.py index 204e7dafc..3edc8fc79 100644 --- a/py/impl/test/looponfail/remote.py +++ b/py/impl/test/looponfail/remote.py @@ -142,7 +142,7 @@ def slave_runsession(channel, config, fullwidth, hasmarkup): continue colitems.append(colitem) else: - colitems = None + colitems = config.getinitialnodes() session.shouldclose = channel.isclosed class Failures(list): diff --git a/py/impl/test/session.py b/py/impl/test/session.py index a16c0bd62..6e417f5d0 100644 --- a/py/impl/test/session.py +++ b/py/impl/test/session.py @@ -93,15 +93,8 @@ class Session(object): exitstatus=exitstatus, ) - def getinitialitems(self, colitems): - if colitems is None: - colitems = [self.config.getfsnode(arg) - for arg in self.config.args] - return colitems - - def main(self, colitems=None): + def main(self, colitems): """ main loop for running tests. """ - colitems = self.getinitialitems(colitems) self.shouldstop = False self.sessionstarts() exitstatus = outcome.EXIT_OK diff --git a/py/plugin/pytest_default.py b/py/plugin/pytest_default.py index b9975991e..127bed7a4 100644 --- a/py/plugin/pytest_default.py +++ b/py/plugin/pytest_default.py @@ -26,7 +26,7 @@ def pytest_collect_file(path, parent): ext = path.ext pb = path.purebasename if pb.startswith("test_") or pb.endswith("_test") or \ - path in parent.config.args: + path in parent.config._argfspaths: if ext == ".py": return parent.Module(path, parent=parent) @@ -41,7 +41,7 @@ def pytest_collect_directory(path, parent): # define Directory(dir) already if not parent.recfilter(path): # by default special ".cvs", ... # check if cmdline specified this dir or a subdir directly - for arg in parent.config.args: + for arg in parent.config._argfspaths: if path == arg or arg.relto(path): break else: diff --git a/py/plugin/pytest_pytester.py b/py/plugin/pytest_pytester.py index a2bff6deb..5c1278cda 100644 --- a/py/plugin/pytest_pytester.py +++ b/py/plugin/pytest_pytester.py @@ -158,7 +158,7 @@ class TmpTestdir: config = self.parseconfig(*args) session = config.initsession() rec = self.getreportrecorder(config) - colitems = [config.getfsnode(arg) for arg in config.args] + colitems = [config.getnode(arg) for arg in config.args] items = list(session.genitems(colitems)) return items, rec @@ -190,7 +190,8 @@ class TmpTestdir: config.pluginmanager.do_configure(config) session = config.initsession() reprec = self.getreportrecorder(config) - session.main() + colitems = config.getinitialnodes() + session.main(colitems) config.pluginmanager.do_unconfigure(config) return reprec @@ -251,7 +252,7 @@ class TmpTestdir: def getfscol(self, path, configargs=()): self.config = self.parseconfig(path, *configargs) self.session = self.config.initsession() - return self.config.getfsnode(path) + return self.config.getnode(path) def getmodulecol(self, source, configargs=(), withinit=False): kw = {self.request.function.__name__: py.code.Source(source).strip()} @@ -266,7 +267,7 @@ class TmpTestdir: plugin = self.config.pluginmanager.getplugin("runner") plugin.pytest_configure(config=self.config) - return self.config.getfsnode(path) + return self.config.getnode(path) def popen(self, cmdargs, stdout, stderr, **kw): if not hasattr(py.std, 'subprocess'): diff --git a/testing/pytest/dist/test_dsession.py b/testing/pytest/dist/test_dsession.py index 74448ca76..b013553bb 100644 --- a/testing/pytest/dist/test_dsession.py +++ b/testing/pytest/dist/test_dsession.py @@ -396,7 +396,7 @@ class TestDSession: config = testdir.parseconfig('-d', p1, '--tx=popen') dsession = DSession(config) hookrecorder = testdir.getreportrecorder(config).hookrecorder - dsession.main([config.getfsnode(p1)]) + dsession.main([config.getnode(p1)]) rep = hookrecorder.popcall("pytest_runtest_logreport").report assert rep.passed rep = hookrecorder.popcall("pytest_runtest_logreport").report diff --git a/testing/pytest/test_collect.py b/testing/pytest/test_collect.py index 496774b65..e04c9f8ac 100644 --- a/testing/pytest/test_collect.py +++ b/testing/pytest/test_collect.py @@ -66,7 +66,7 @@ class TestCollector: return MyDirectory(path, parent=parent) """) config = testdir.parseconfig(hello) - node = config.getfsnode(hello) + node = config.getnode(hello) assert isinstance(node, py.test.collect.File) assert node.name == "hello.xxx" names = config._rootcol.totrail(node) @@ -84,7 +84,7 @@ class TestCollectFS: tmpdir.ensure("normal", 'test_found.py') tmpdir.ensure('test_found.py') - col = testdir.parseconfig(tmpdir).getfsnode(tmpdir) + col = testdir.parseconfig(tmpdir).getnode(tmpdir) items = col.collect() names = [x.name for x in items] assert len(items) == 2 @@ -93,7 +93,7 @@ class TestCollectFS: def test_found_certain_testfiles(self, testdir): p1 = testdir.makepyfile(test_found = "pass", found_test="pass") - col = testdir.parseconfig(p1).getfsnode(p1.dirpath()) + col = testdir.parseconfig(p1).getnode(p1.dirpath()) items = col.collect() # Directory collect returns files sorted by name assert len(items) == 2 assert items[1].name == 'test_found.py' @@ -106,7 +106,7 @@ class TestCollectFS: testdir.makepyfile(test_two="hello") p1.dirpath().mkdir("dir2") config = testdir.parseconfig() - col = config.getfsnode(p1.dirpath()) + col = config.getnode(p1.dirpath()) names = [x.name for x in col.collect()] assert names == ["dir1", "dir2", "test_one.py", "test_two.py", "x"] @@ -120,7 +120,7 @@ class TestCollectPluginHookRelay: config = testdir.Config() config.pluginmanager.register(Plugin()) config.parse([tmpdir]) - col = config.getfsnode(tmpdir) + col = config.getnode(tmpdir) testdir.makefile(".abc", "xyz") res = col.collect() assert len(wascalled) == 1 @@ -203,20 +203,36 @@ class TestRootCol: tmpdir.ensure("a", "__init__.py") x = tmpdir.ensure("a", "trail.py") config = testdir.reparseconfig([x]) - col = config.getfsnode(x) + col = config.getnode(x) trail = config._rootcol.totrail(col) col2 = config._rootcol.fromtrail(trail) assert col2 == col def test_totrail_topdir_and_beyond(self, testdir, tmpdir): config = testdir.reparseconfig() - col = config.getfsnode(config.topdir) + col = config.getnode(config.topdir) trail = config._rootcol.totrail(col) col2 = config._rootcol.fromtrail(trail) assert col2.fspath == config.topdir assert len(col2.listchain()) == 1 - py.test.raises(config.Error, "config.getfsnode(config.topdir.dirpath())") - #col3 = config.getfsnode(config.topdir.dirpath()) + py.test.raises(config.Error, "config.getnode(config.topdir.dirpath())") + #col3 = config.getnode(config.topdir.dirpath()) #py.test.raises(ValueError, # "col3._totrail()") + def test_argid(self, testdir, tmpdir): + cfg = testdir.parseconfig() + p = testdir.makepyfile("def test_func(): pass") + item = cfg.getnode("%s::test_func" % p) + assert item.name == "test_func" + + def test_argid_with_method(self, testdir, tmpdir): + cfg = testdir.parseconfig() + p = testdir.makepyfile(""" + class TestClass: + def test_method(self): pass + """) + item = cfg.getnode("%s::TestClass::()::test_method" % p) + assert item.name == "test_method" + item = cfg.getnode("%s::TestClass::test_method" % p) + assert item.name == "test_method" diff --git a/testing/pytest/test_config.py b/testing/pytest/test_config.py index 71276ddbc..ece7beabe 100644 --- a/testing/pytest/test_config.py +++ b/testing/pytest/test_config.py @@ -117,28 +117,28 @@ class TestConfigAPI: py.test.raises(ValueError, "config.setsessionclass(Session1)") -class TestConfigApi_getcolitems: - def test_getcolitems_onedir(self, testdir): +class TestConfigApi_getinitialnodes: + def test_onedir(self, testdir): config = testdir.reparseconfig([testdir.tmpdir]) - colitems = config.getcolitems() + colitems = config.getinitialnodes() assert len(colitems) == 1 col = colitems[0] assert isinstance(col, py.test.collect.Directory) for col in col.listchain(): assert col.config is config - def test_getcolitems_twodirs(self, testdir, tmpdir): + def test_twodirs(self, testdir, tmpdir): config = testdir.reparseconfig([tmpdir, tmpdir]) - colitems = config.getcolitems() + colitems = config.getinitialnodes() assert len(colitems) == 2 col1, col2 = colitems assert col1.name == col2.name assert col1.parent == col2.parent - def test_getcolitems_curdir_and_subdir(self, testdir, tmpdir): + def test_curdir_and_subdir(self, testdir, tmpdir): a = tmpdir.ensure("a", dir=1) config = testdir.reparseconfig([tmpdir, a]) - colitems = config.getcolitems() + colitems = config.getinitialnodes() assert len(colitems) == 2 col1, col2 = colitems assert col1.name == tmpdir.basename @@ -147,10 +147,10 @@ class TestConfigApi_getcolitems: for subcol in col.listchain(): assert col.config is config - def test__getcol_global_file(self, testdir, tmpdir): + def test_global_file(self, testdir, tmpdir): x = tmpdir.ensure("x.py") config = testdir.reparseconfig([x]) - col = config.getfsnode(x) + col, = config.getinitialnodes() assert isinstance(col, py.test.collect.Module) assert col.name == 'x.py' assert col.parent.name == tmpdir.basename @@ -158,21 +158,21 @@ class TestConfigApi_getcolitems: for col in col.listchain(): assert col.config is config - def test__getcol_global_dir(self, testdir, tmpdir): + def test_global_dir(self, testdir, tmpdir): x = tmpdir.ensure("a", dir=1) config = testdir.reparseconfig([x]) - col = config.getfsnode(x) + col, = config.getinitialnodes() assert isinstance(col, py.test.collect.Directory) print(col.listchain()) assert col.name == 'a' assert isinstance(col.parent, RootCollector) assert col.config is config - def test__getcol_pkgfile(self, testdir, tmpdir): + def test_pkgfile(self, testdir, tmpdir): x = tmpdir.ensure("x.py") tmpdir.ensure("__init__.py") config = testdir.reparseconfig([x]) - col = config.getfsnode(x) + col, = config.getinitialnodes() assert isinstance(col, py.test.collect.Module) assert col.name == 'x.py' assert col.parent.name == x.dirpath().basename @@ -214,7 +214,9 @@ class TestConfig_gettopdir: Z = tmp.ensure('Z', dir=1) assert gettopdir([c]) == a assert gettopdir([c, Z]) == tmp - + assert gettopdir(["%s::xyc" % c]) == a + assert gettopdir(["%s::xyc::abc" % c]) == a + assert gettopdir(["%s::xyc" % c, "%s::abc" % Z]) == tmp def test_options_on_small_file_do_not_blow_up(testdir): def runfiletest(opts): diff --git a/testing/pytest/test_deprecated_api.py b/testing/pytest/test_deprecated_api.py index e5305f672..743029f00 100644 --- a/testing/pytest/test_deprecated_api.py +++ b/testing/pytest/test_deprecated_api.py @@ -50,7 +50,7 @@ class TestCollectDeprecated: def check2(self): pass """)) config = testdir.parseconfig(somefile) - dirnode = config.getfsnode(somefile.dirpath()) + dirnode = config.getnode(somefile.dirpath()) colitems = dirnode.collect() w = recwarn.pop(DeprecationWarning) assert w.filename.find("conftest.py") != -1 @@ -174,7 +174,7 @@ class TestCollectDeprecated: """) testme = testdir.makefile('xxx', testme="hello") config = testdir.parseconfig(testme) - col = config.getfsnode(testme) + col = config.getnode(testme) assert col.collect() == [] @@ -236,11 +236,11 @@ class TestDisabled: """) config = testdir.parseconfig() if name == "Directory": - config.getfsnode(testdir.tmpdir) + config.getnode(testdir.tmpdir) elif name in ("Module", "File"): - config.getfsnode(p) + config.getnode(p) else: - fnode = config.getfsnode(p) + fnode = config.getnode(p) recwarn.clear() fnode.collect() w = recwarn.pop(DeprecationWarning) @@ -317,7 +317,7 @@ def test_conftest_non_python_items(recwarn, testdir): testdir.maketxtfile(x="") config = testdir.parseconfig() recwarn.clear() - dircol = config.getfsnode(checkfile.dirpath()) + dircol = config.getnode(checkfile.dirpath()) w = recwarn.pop(DeprecationWarning) assert str(w.message).find("conftest.py") != -1 colitems = dircol.collect() @@ -325,7 +325,7 @@ def test_conftest_non_python_items(recwarn, testdir): assert colitems[0].name == "hello.xxx" assert colitems[0].__class__.__name__ == "CustomItem" - item = config.getfsnode(checkfile) + item = config.getnode(checkfile) assert item.name == "hello.xxx" assert item.__class__.__name__ == "CustomItem" @@ -358,14 +358,14 @@ def test_extra_python_files_and_functions(testdir): """) # check that directory collects "check_" files config = testdir.parseconfig() - col = config.getfsnode(checkfile.dirpath()) + col = config.getnode(checkfile.dirpath()) colitems = col.collect() assert len(colitems) == 1 assert isinstance(colitems[0], py.test.collect.Module) # check that module collects "check_" functions and methods config = testdir.parseconfig(checkfile) - col = config.getfsnode(checkfile) + col = config.getnode(checkfile) assert isinstance(col, py.test.collect.Module) colitems = col.collect() assert len(colitems) == 2 diff --git a/testing/pytest/test_pickling.py b/testing/pytest/test_pickling.py index 5eedd0c92..e8f5d29c3 100644 --- a/testing/pytest/test_pickling.py +++ b/testing/pytest/test_pickling.py @@ -75,8 +75,10 @@ class TestConfigPickling: config2 = Config() config2.__setstate__(config1.__getstate__()) assert config2.topdir == py.path.local() - config2_relpaths = [x.relto(config2.topdir) for x in config2.args] - config1_relpaths = [x.relto(config1.topdir) for x in config1.args] + config2_relpaths = [py.path.local(x).relto(config2.topdir) + for x in config2.args] + config1_relpaths = [py.path.local(x).relto(config1.topdir) + for x in config1.args] assert config2_relpaths == config1_relpaths for name, value in config1.option.__dict__.items(): @@ -138,7 +140,7 @@ class TestConfigPickling: testdir.chdir() testdir.makepyfile(hello="def test_x(): pass") config = testdir.parseconfig(tmpdir) - col = config.getfsnode(config.topdir) + col = config.getnode(config.topdir) io = py.io.BytesIO() pickler = pickle.Pickler(io) pickler.dump(col) @@ -152,7 +154,7 @@ class TestConfigPickling: tmpdir = testdir.tmpdir dir1 = tmpdir.ensure("somedir", dir=1) config = testdir.parseconfig() - col = config.getfsnode(config.topdir) + col = config.getnode(config.topdir) col1 = col.join(dir1.basename) assert col1.parent is col io = py.io.BytesIO()