diff --git a/pythonFiles/testing_tools/adapter/discovery.py b/pythonFiles/testing_tools/adapter/discovery.py new file mode 100644 index 000000000000..15196a6b0beb --- /dev/null +++ b/pythonFiles/testing_tools/adapter/discovery.py @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from __future__ import absolute_import, print_function + +import os.path + +from .info import ParentInfo + + + +class DiscoveredTests(object): + """A container for the discovered tests and their parents.""" + + def __init__(self): + self.reset() + + def __len__(self): + return len(self._tests) + + def __getitem__(self, index): + return self._tests[index] + + @property + def parents(self): + return sorted(self._parents.values(), key=lambda v: (v.root or v.name, v.id)) + + def reset(self): + """Clear out any previously discovered tests.""" + self._parents = {} + self._tests = [] + + def add_test(self, test, parents): + """Add the given test and its parents.""" + parentid = self._ensure_parent(test.path, parents) + # Updating the parent ID and the test ID aren't necessary if the + # provided test and parents (from the test collector) are + # properly generated. However, we play it safe here. + test = test._replace(parentid=parentid) + if not test.id.startswith('.' + os.path.sep): + test = test._replace(id=os.path.join('.', test.id)) + self._tests.append(test) + + def _ensure_parent(self, path, parents): + rootdir = path.root + + _parents = iter(parents) + nodeid, name, kind = next(_parents) + # As in add_test(), the node ID *should* already be correct. + if nodeid != '.' and not nodeid.startswith('.' + os.path.sep): + nodeid = os.path.join('.', nodeid) + _parentid = nodeid + for parentid, parentname, parentkind in _parents: + # As in add_test(), the parent ID *should* already be correct. + if parentid != '.' and not parentid.startswith('.' + os.path.sep): + parentid = os.path.join('.', parentid) + info = ParentInfo(nodeid, kind, name, rootdir, parentid) + self._parents[(rootdir, nodeid)] = info + nodeid, name, kind = parentid, parentname, parentkind + assert nodeid == '.' + info = ParentInfo(nodeid, kind, name=rootdir) + self._parents[(rootdir, nodeid)] = info + + return _parentid diff --git a/pythonFiles/testing_tools/adapter/pytest/_discovery.py b/pythonFiles/testing_tools/adapter/pytest/_discovery.py index 1370298ffd6b..5efac3a388ec 100644 --- a/pythonFiles/testing_tools/adapter/pytest/_discovery.py +++ b/pythonFiles/testing_tools/adapter/pytest/_discovery.py @@ -8,8 +8,7 @@ import pytest -from .. import util -from ..info import ParentInfo +from .. import util, discovery from ._pytest_item import parse_item @@ -63,7 +62,7 @@ class TestCollector(object): def __init__(self, tests=None): if tests is None: - tests = DiscoveredTests() + tests = discovery.DiscoveredTests() self._tests = tests self._started = False @@ -74,8 +73,8 @@ def pytest_collection_modifyitems(self, session, config, items): self._started = True self._tests.reset() for item in items: - test, suiteids = parse_item(item, self.NORMCASE, self.PATHSEP) - self._tests.add_test(test, suiteids) + test, parents = parse_item(item, self.NORMCASE, self.PATHSEP) + self._tests.add_test(test, parents) # This hook is not specified in the docs, so we also provide # the "modifyitems" hook just in case. @@ -88,111 +87,5 @@ def pytest_collection_finish(self, session): return self._tests.reset() for item in items: - test, suiteids = parse_item(item, self.NORMCASE, self.PATHSEP) - self._tests.add_test(test, suiteids) - - -class DiscoveredTests(object): - """A container for the discovered tests and their parents.""" - - def __init__(self): - self.reset() - - def __len__(self): - return len(self._tests) - - def __getitem__(self, index): - return self._tests[index] - - @property - def parents(self): - return sorted(self._parents.values(), key=lambda v: (v.root or v.name, v.id)) - - def reset(self): - """Clear out any previously discovered tests.""" - self._parents = {} - self._tests = [] - - def add_test(self, test, suiteids): - """Add the given test and its parents.""" - parentid = self._ensure_parent(test.path, test.parentid, suiteids) - test = test._replace(parentid=parentid) - if not test.id.startswith('.' + os.path.sep): - test = test._replace(id=os.path.join('.', test.id)) - self._tests.append(test) - - def _ensure_parent(self, path, parentid, suiteids): - if not parentid.startswith('.' + os.path.sep): - parentid = os.path.join('.', parentid) - fileid = self._ensure_file(path.root, path.relfile) - rootdir = path.root - - if not path.func: - return parentid - - fullsuite, _, funcname = path.func.rpartition('.') - suiteid = self._ensure_suites(fullsuite, rootdir, fileid, suiteids) - parent = suiteid if suiteid else fileid - - if path.sub: - if (rootdir, parentid) not in self._parents: - funcinfo = ParentInfo(parentid, 'function', funcname, - rootdir, parent) - self._parents[(rootdir, parentid)] = funcinfo - elif parent != parentid: - print(parent, parentid) - # TODO: What to do? - raise NotImplementedError - return parentid - - def _ensure_file(self, rootdir, relfile): - if (rootdir, '.') not in self._parents: - self._parents[(rootdir, '.')] = ParentInfo('.', 'folder', rootdir) - if relfile.startswith('.' + os.path.sep): - fileid = relfile - else: - fileid = relfile = os.path.join('.', relfile) - - if (rootdir, fileid) not in self._parents: - folderid, filebase = os.path.split(fileid) - fileinfo = ParentInfo(fileid, 'file', filebase, rootdir, folderid) - self._parents[(rootdir, fileid)] = fileinfo - - while folderid != '.' and (rootdir, folderid) not in self._parents: - parentid, name = os.path.split(folderid) - folderinfo = ParentInfo(folderid, 'folder', name, rootdir, parentid) - self._parents[(rootdir, folderid)] = folderinfo - folderid = parentid - return relfile - - def _ensure_suites(self, fullsuite, rootdir, fileid, suiteids): - if not fullsuite: - if suiteids: - print(suiteids) - # TODO: What to do? - raise NotImplementedError - return None - if len(suiteids) != fullsuite.count('.') + 1: - print(suiteids) - # TODO: What to do? - raise NotImplementedError - - suiteid = suiteids.pop() - if not suiteid.startswith('.' + os.path.sep): - suiteid = os.path.join('.', suiteid) - final = suiteid - while '.' in fullsuite and (rootdir, suiteid) not in self._parents: - parentid = suiteids.pop() - if not parentid.startswith('.' + os.path.sep): - parentid = os.path.join('.', parentid) - fullsuite, _, name = fullsuite.rpartition('.') - suiteinfo = ParentInfo(suiteid, 'suite', name, rootdir, parentid) - self._parents[(rootdir, suiteid)] = suiteinfo - - suiteid = parentid - else: - name = fullsuite - suiteinfo = ParentInfo(suiteid, 'suite', name, rootdir, fileid) - if (rootdir, suiteid) not in self._parents: - self._parents[(rootdir, suiteid)] = suiteinfo - return final + test, parents = parse_item(item, self.NORMCASE, self.PATHSEP) + self._tests.add_test(test, parents) diff --git a/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py b/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py index 313b40372052..92b48e820d4e 100644 --- a/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py +++ b/pythonFiles/testing_tools/adapter/pytest/_pytest_item.py @@ -95,9 +95,45 @@ import sys +import pytest +import _pytest.doctest +import _pytest.unittest + from ..info import TestInfo, TestPath +def should_never_reach_here(node, *extra): + """Indicates a code path we should never reach.""" + print('The Python extension has run into an unexpected situation') + print('while processing a pytest node during test discovery. Please') + print('Please open an issue at:') + print(' https://github.com/microsoft/vscode-python/issues') + print('and paste the following output there.') + print() + for field, info in _summarize_item(node): + print('{}: {}'.format(field, info)) + if extra: + print() + print('extra info:') + for info in extra: + if isinstance(line, str): + print(str) + else: + try: + print(*line) + except TypeError: + print(line) + print() + print('traceback:') + import traceback + traceback.print_stack() + + msg = 'Unexpected pytest node (see printed output).' + exc = NotImplementedError(msg) + exc.node = node + return exc + + def parse_item(item, _normcase, _pathsep): """Return (TestInfo, [suite ID]) for the given item. @@ -109,50 +145,45 @@ def parse_item(item, _normcase, _pathsep): """ #_debug_item(item, showsummary=True) kind, _ = _get_item_kind(item) - # Figure out the func, suites, and subs. - (nodeid, fileid, suiteids, suites, funcid, basename, parameterized + (nodeid, parents, fileid, testfunc, parameterized ) = _parse_node_id(item.nodeid, kind, _pathsep, _normcase) - if kind == 'function': - funcname = basename - # Note: funcname does not necessarily match item.function.__name__. - # This can result from importing a test function from another module. - if suites: - testfunc = '.'.join(suites) + '.' + funcname - else: - testfunc = funcname - elif kind == 'doctest': - testfunc = None - funcname = None + # Note: testfunc does not necessarily match item.function.__name__. + # This can result from importing a test function from another module. # Figure out the file. - relfile = _normcase(fileid) - fspath = str(item.fspath) - if not _normcase(fspath).endswith(relfile[1:]): - print(fspath) - print(relfile) - raise NotImplementedError - testroot = str(item.fspath)[:-len(relfile) + 1] + relfile = fileid + fspath = _normcase(str(item.fspath)) + if not fspath.endswith(relfile[1:]): + raise should_never_reach_here( + item, + fspath, + relfile, + ) + testroot = fspath[:-len(relfile) + 1] location, fullname = _get_location(item, relfile, _normcase, _pathsep) if kind == 'function': if testfunc and fullname != testfunc + parameterized: - print(item.nodeid) - print(fullname, suites, testfunc) - # TODO: What to do? - raise NotImplementedError + raise should_never_reach_here( + item, + fullname, + testfunc, + parameterized, + ) elif kind == 'doctest': - if testfunc and fullname != testfunc + parameterized: - print(item.nodeid) - print(fullname, testfunc) - # TODO: What to do? - raise NotImplementedError + if (testfunc and fullname != testfunc and + fullname != '[doctest] ' + testfunc): + raise should_never_reach_here( + item, + fullname, + testfunc, + ) + testfunc = None # Sort out the parent. - if parameterized: - parentid = funcid - elif suites: - parentid = suiteids[-1] + if parents: + parentid, _, _ = parents[0] else: - parentid = fileid + parentid = None # Sort out markers. # See: https://docs.pytest.org/en/latest/reference.html#marks @@ -167,7 +198,7 @@ def parse_item(item, _normcase, _pathsep): markers.add('skip-if') elif marker.name == 'xfail': markers.add('expected-failure') - # TODO: Support other markers? + # We can add support for other markers as we need them? test = TestInfo( id=nodeid, @@ -182,7 +213,9 @@ def parse_item(item, _normcase, _pathsep): markers=sorted(markers) if markers else None, parentid=parentid, ) - return test, suiteids + if parents and parents[-1] == ('.', None, 'folder'): # This should always be true? + parents[-1] = ('.', testroot, 'folder') + return test, parents def _get_location(item, relfile, _normcase, _pathsep): @@ -224,75 +257,132 @@ def _find_location(srcfile, lineno, relfile, func, _pathsep): return srcfile, lineno -def _parse_node_id(nodeid, kind, _pathsep, _normcase): +def _parse_node_id(testid, kind, _pathsep, _normcase): """Return the components of the given node ID, in heirarchical order.""" - if not nodeid.startswith('.' + _pathsep): - nodeid = '.' + _pathsep + nodeid - while '::()::' in nodeid: - nodeid = nodeid.replace('::()::', '::') - - fileid, _, remainder = nodeid.partition('::') - if not fileid or not remainder: - print(nodeid) - # TODO: Unexpected! What to do? - raise NotImplementedError - fileid = _normcase(fileid) - nodeid = fileid + '::' + remainder + nodes = iter(_iter_nodes(testid, kind, _pathsep, _normcase)) + testid, name, kind = next(nodes) + parents = [] + parameterized = None if kind == 'doctest': - try: - parentid, name = nodeid.split('::') - except ValueError: - print(nodeid) - # TODO: Unexpected! What to do? - raise NotImplementedError - funcid = None - parameterized = '' + parents = list(nodes) + fileid, _, _ = parents[0] + return testid, parents, fileid, name, parameterized + elif kind is None: + fullname = None else: - parameterized = '' - if nodeid.endswith(']'): - funcid, sep, parameterized = nodeid.partition('[') - if not sep: - print(nodeid) - # TODO: Unexpected! What to do? - raise NotImplementedError - parameterized = sep + parameterized + if kind == 'subtest': + node = next(nodes) + parents.append(node) + funcid, funcname, _ = node + parameterized = testid[len(funcid):] + elif kind == 'function': + funcname = name + else: + raise should_never_reach_here( + testid, + kind, + ) + fullname = funcname + + for node in nodes: + parents.append(node) + parentid, name, kind = node + if kind == 'file': + fileid = parentid + break + elif fullname is None: + # We don't guess how to interpret the node ID for these tests. + continue + elif kind == 'suite': + fullname = name + '.' + fullname else: - funcid = nodeid - parentid, _, name = funcid.rpartition('::') - if not parentid or not name: - print(parentid, name) - # TODO: What to do? We expect at least a filename and a function - raise NotImplementedError - - suites = [] - suiteids = [] + raise should_never_reach_here( + testid, + node, + ) + else: + fileid = None + parents.extend(nodes) # Add the rest in as-is. + + return testid, parents, fileid, fullname, parameterized or '' + + +def _iter_nodes(nodeid, kind, _pathsep, _normcase): + """Yield (nodeid, name, kind) for the given node ID and its parents.""" + nodeid = _normalize_node_id(nodeid, kind, _pathsep, _normcase) + + if kind == 'function' and nodeid.endswith(']'): + funcid, sep, parameterized = nodeid.partition('[') + if not sep: + raise should_never_reach_here( + nodeid, + ) + yield (nodeid, sep + parameterized, 'subtest') + nodeid = funcid + + parentid, _, name = nodeid.rpartition('::') + if not parentid: + if kind is None: + # This assumes that plugins can generate nodes that do not + # have a parent. All the builtin nodes have one. + yield (nodeid, name, kind) + return + # We expect at least a filename and a name. + raise should_never_reach_here( + nodeid, + ) + yield (nodeid, name, kind) + + # Extract the suites. while '::' in parentid: - fullid = parentid - parentid, _, suitename = fullid.rpartition('::') - suiteids.insert(0, fullid) - suites.insert(0, suitename) - if parentid != fileid: - print(nodeid) - print(parentid, fileid) - - return nodeid, fileid, suiteids, suites, funcid, name, parameterized + suiteid = parentid + parentid, _, name = parentid.rpartition('::') + yield (suiteid, name, 'suite') + + # Extract the file and folders. + fileid = parentid + parentid, _, filename = fileid.rpartition(_pathsep) + yield (fileid, filename, 'file') + # We're guaranteed at least one (the test root). + while _pathsep in parentid: + folderid = parentid + parentid, _, foldername = folderid.rpartition(_pathsep) + yield (folderid, foldername, 'folder') + # We set the actual test root later at the bottom of parse_item(). + testroot = None + yield (parentid, testroot, 'folder') + + +def _normalize_node_id(nodeid, kind, _pathsep, _normcase): + """Return the canonical form for the given node ID.""" + while '::()::' in nodeid: + nodeid = nodeid.replace('::()::', '::') + if kind is None: + return nodeid + + fileid, sep, remainder = nodeid.partition('::') + if sep: + # pytest works fine even if we normalize the filename. + nodeid = _normcase(fileid) + sep + remainder + + if nodeid.startswith(_pathsep): + raise should_never_reach_here( + nodeid, + ) + if not nodeid.startswith('.' + _pathsep): + nodeid = '.' + _pathsep + nodeid + return nodeid def _get_item_kind(item): """Return (kind, isunittest) for the given item.""" - try: - itemtype = item.kind - except AttributeError: - itemtype = item.__class__.__name__ - - if itemtype == 'DoctestItem': + if isinstance(item, _pytest.doctest.DoctestItem): return 'doctest', False - elif itemtype == 'Function': - return 'function', False - elif itemtype == 'TestCaseFunction': + elif isinstance(item, _pytest.unittest.TestCaseFunction): return 'function', True - elif item.hasattr('function'): + elif isinstance(item, pytest.Function): + # We *could* be more specific, e.g. "method", "subtest". return 'function', False else: return None, False @@ -301,23 +391,46 @@ def _get_item_kind(item): ############################# # useful for debugging +_FIELDS = [ + 'nodeid', + 'kind', + 'class', + 'name', + 'fspath', + 'location', + 'function', + 'markers', + 'user_properties', + 'attrnames', + ] + + +def _summarize_item(item): + if not hasattr(item, 'nodeid'): + yield 'nodeid', item + return + + for field in _FIELDS: + try: + if field == 'kind': + yield field,_get_item_kind(item) + elif field == 'class': + yield field, item.__class__.__name__ + elif field == 'markers': + yield field, item.own_markers + #yield field, list(item.iter_markers()) + elif field == 'attrnames': + yield field, dir(item) + else: + yield field, getattr(item, field, '') + except Exception as exc: + yield field, '' + + def _debug_item(item, showsummary=False): item._debugging = True try: - # TODO: Make a PytestTest class to wrap the item? - summary = { - 'id': item.nodeid, - 'kind': _get_item_kind(item), - 'class': item.__class__.__name__, - 'name': item.name, - 'fspath': item.fspath, - 'location': item.location, - 'func': getattr(item, 'function', None), - 'markers': item.own_markers, - #'markers': list(item.iter_markers()), - 'props': item.user_properties, - 'attrnames': dir(item), - } + summary = dict(_summarize_item(item)) finally: item._debugging = False diff --git a/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py b/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py index 262d9514b095..979ba50d70c2 100644 --- a/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py +++ b/pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py @@ -12,12 +12,16 @@ import sys import unittest +import pytest +import _pytest.doctest + from ....util import Stub, StubProxy from testing_tools.adapter.info import TestInfo, TestPath, ParentInfo -from testing_tools.adapter.pytest._discovery import ( - discover, TestCollector, DiscoveredTests - ) -import pytest +from testing_tools.adapter.pytest._discovery import discover, TestCollector + + +def fix_path(nodeid): + return nodeid.replace('/', os.path.sep) class StubPyTest(StubProxy): @@ -74,8 +78,8 @@ def parents(self): def reset(self): self.add_call('reset', None, None) - def add_test(self, test, suiteids): - self.add_call('add_test', None, {'test': test, 'suiteids': suiteids}) + def add_test(self, test, parents): + self.add_call('add_test', None, {'test': test, 'parents': parents}) class FakeFunc(object): @@ -92,8 +96,6 @@ def __init__(self, name): class StubPytestItem(StubProxy): - kind = 'Function' - _debugging = False _hasfunc = True @@ -110,6 +112,9 @@ def __init__(self, stub=None, **attrs): if 'own_markers' not in attrs: self.own_markers = () + def __repr__(self): + return object.__repr__(self) + def __getattr__(self, name): if not self._debugging: self.add_call(name + ' (attr)', None, None) @@ -121,6 +126,29 @@ def func(*args, **kwargs): return func +class StubSubtypedItem(StubPytestItem): + + def __init__(self, *args, **kwargs): + super(StubSubtypedItem, self).__init__(*args, **kwargs) + if 'nodeid' in self.__dict__: + self._nodeid = self.__dict__.pop('nodeid') + + @property + def location(self): + return self.__dict__.get('location') + + +class StubFunctionItem(StubSubtypedItem, pytest.Function): + + @property + def function(self): + return self.__dict__.get('function') + + +class StubDoctestItem(StubSubtypedItem, _pytest.doctest.DoctestItem): + pass + + class StubPytestSession(StubProxy): def __init__(self, stub=None): @@ -275,13 +303,13 @@ def test_modifyitems(self): config = StubPytestConfig(stub) collector = TestCollector(tests=discovered) - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile1 = './test_spam.py'.replace('/', os.path.sep) - relfile2 = 'x/y/z/test_eggs.py'.replace('/', os.path.sep) + testroot = fix_path('/a/b/c') + relfile1 = fix_path('./test_spam.py') + relfile2 = fix_path('x/y/z/test_eggs.py') relfileid2 = os.path.join('.', relfile2) collector.pytest_collection_modifyitems(session, config, [ - StubPytestItem( + StubFunctionItem( stub, nodeid='test_spam.py::SpamTests::test_one', name='test_one', @@ -289,7 +317,7 @@ def test_modifyitems(self): fspath=os.path.join(testroot, 'test_spam.py'), function=FakeFunc('test_one'), ), - StubPytestItem( + StubFunctionItem( stub, nodeid='test_spam.py::SpamTests::test_other', name='test_other', @@ -297,7 +325,7 @@ def test_modifyitems(self): fspath=os.path.join(testroot, 'test_spam.py'), function=FakeFunc('test_other'), ), - StubPytestItem( + StubFunctionItem( stub, nodeid='test_spam.py::test_all', name='test_all', @@ -305,7 +333,7 @@ def test_modifyitems(self): fspath=os.path.join(testroot, 'test_spam.py'), function=FakeFunc('test_all'), ), - StubPytestItem( + StubFunctionItem( stub, nodeid='test_spam.py::test_each[10-10]', name='test_each[10-10]', @@ -313,7 +341,7 @@ def test_modifyitems(self): fspath=os.path.join(testroot, 'test_spam.py'), function=FakeFunc('test_each'), ), - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile2 + '::All::BasicTests::test_first', name='test_first', @@ -321,7 +349,7 @@ def test_modifyitems(self): fspath=os.path.join(testroot, relfile2), function=FakeFunc('test_first'), ), - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile2 + '::All::BasicTests::test_each[1+2-3]', name='test_each[1+2-3]', @@ -345,7 +373,11 @@ def test_modifyitems(self): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[relfile1 + '::SpamTests'], + parents=[ + (relfile1 + '::SpamTests', 'SpamTests', 'suite'), + (relfile1, 'test_spam.py', 'file'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfile1 + '::SpamTests::test_one', name='test_one', @@ -361,7 +393,11 @@ def test_modifyitems(self): ), )), ('discovered.add_test', None, dict( - suiteids=[relfile1 + '::SpamTests'], + parents=[ + (relfile1 + '::SpamTests', 'SpamTests', 'suite'), + (relfile1, 'test_spam.py', 'file'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfile1 + '::SpamTests::test_other', name='test_other', @@ -377,7 +413,10 @@ def test_modifyitems(self): ), )), ('discovered.add_test', None, dict( - suiteids=[], + parents=[ + (relfile1, 'test_spam.py', 'file'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfile1 + '::test_all', name='test_all', @@ -393,7 +432,11 @@ def test_modifyitems(self): ), )), ('discovered.add_test', None, dict( - suiteids=[], + parents=[ + (relfile1 + '::test_each', 'test_each', 'function'), + (relfile1, 'test_spam.py', 'file'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfile1 + '::test_each[10-10]', name='test_each[10-10]', @@ -409,8 +452,15 @@ def test_modifyitems(self): ), )), ('discovered.add_test', None, dict( - suiteids=[relfileid2 + '::All', - relfileid2 + '::All::BasicTests'], + parents=[ + (relfileid2 + '::All::BasicTests', 'BasicTests', 'suite'), + (relfileid2 + '::All', 'All', 'suite'), + (relfileid2, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid2 + '::All::BasicTests::test_first', name='test_first', @@ -426,8 +476,16 @@ def test_modifyitems(self): ), )), ('discovered.add_test', None, dict( - suiteids=[relfileid2 + '::All', - relfileid2 + '::All::BasicTests'], + parents=[ + (relfileid2 + '::All::BasicTests::test_each', 'test_each', 'function'), + (relfileid2 + '::All::BasicTests', 'BasicTests', 'suite'), + (relfileid2 + '::All', 'All', 'suite'), + (relfileid2, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid2 + '::All::BasicTests::test_each[1+2-3]', name='test_each[1+2-3]', @@ -448,11 +506,11 @@ def test_finish(self): stub = Stub() discovered = StubDiscoveredTests(stub) session = StubPytestSession(stub) - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = 'x/y/z/test_eggs.py'.replace('/', os.path.sep) + testroot = fix_path('/a/b/c') + relfile = fix_path('x/y/z/test_eggs.py') relfileid = os.path.join('.', relfile) session.items = [ - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile + '::SpamTests::test_spam', name='test_spam', @@ -469,7 +527,14 @@ def test_finish(self): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[relfileid + '::SpamTests'], + parents=[ + (relfileid + '::SpamTests', 'SpamTests', 'suite'), + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::SpamTests::test_spam', name='test_spam', @@ -490,44 +555,40 @@ def test_doctest(self): stub = Stub() discovered = StubDiscoveredTests(stub) session = StubPytestSession(stub) - testroot = '/a/b/c'.replace('/', os.path.sep) - doctestfile = 'x/test_doctest.txt'.replace('/', os.path.sep) + testroot = fix_path('/a/b/c') + doctestfile = fix_path('x/test_doctest.txt') doctestfileid = os.path.join('.', doctestfile) - relfile = 'x/y/z/test_eggs.py'.replace('/', os.path.sep) + relfile = fix_path('x/y/z/test_eggs.py') relfileid = os.path.join('.', relfile) session.items = [ - StubPytestItem( + StubDoctestItem( stub, nodeid=doctestfile + '::test_doctest.txt', name='test_doctest.txt', location=(doctestfile, 0, '[doctest] test_doctest.txt'), fspath=os.path.join(testroot, doctestfile), - kind='DoctestItem', ), # With --doctest-modules - StubPytestItem( + StubDoctestItem( stub, nodeid=relfile + '::test_eggs', name='test_eggs', location=(relfile, 0, '[doctest] test_eggs'), fspath=os.path.join(testroot, relfile), - kind='DoctestItem', ), - StubPytestItem( + StubDoctestItem( stub, nodeid=relfile + '::test_eggs.TestSpam', name='test_eggs.TestSpam', location=(relfile, 12, '[doctest] test_eggs.TestSpam'), fspath=os.path.join(testroot, relfile), - kind='DoctestItem', ), - StubPytestItem( + StubDoctestItem( stub, nodeid=relfile + '::test_eggs.TestSpam.TestEggs', name='test_eggs.TestSpam.TestEggs', location=(relfile, 27, '[doctest] test_eggs.TestSpam.TestEggs'), fspath=os.path.join(testroot, relfile), - kind='DoctestItem', ), ] collector = TestCollector(tests=discovered) @@ -538,7 +599,11 @@ def test_doctest(self): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[], + parents=[ + (doctestfileid, 'test_doctest.txt', 'file'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=doctestfileid + '::test_doctest.txt', name='test_doctest.txt', @@ -553,7 +618,13 @@ def test_doctest(self): ), )), ('discovered.add_test', None, dict( - suiteids=[], + parents=[ + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::test_eggs', name='test_eggs', @@ -568,7 +639,13 @@ def test_doctest(self): ), )), ('discovered.add_test', None, dict( - suiteids=[], + parents=[ + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::test_eggs.TestSpam', name='test_eggs.TestSpam', @@ -583,7 +660,13 @@ def test_doctest(self): ), )), ('discovered.add_test', None, dict( - suiteids=[], + parents=[ + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::test_eggs.TestSpam.TestEggs', name='test_eggs.TestSpam.TestEggs', @@ -603,11 +686,11 @@ def test_nested_brackets(self): stub = Stub() discovered = StubDiscoveredTests(stub) session = StubPytestSession(stub) - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = 'x/y/z/test_eggs.py'.replace('/', os.path.sep) + testroot = fix_path('/a/b/c') + relfile = fix_path('x/y/z/test_eggs.py') relfileid = os.path.join('.', relfile) session.items = [ - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile + '::SpamTests::test_spam[a-[b]-c]', name='test_spam[a-[b]-c]', @@ -624,7 +707,15 @@ def test_nested_brackets(self): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[relfileid + '::SpamTests'], + parents=[ + (relfileid + '::SpamTests::test_spam', 'test_spam', 'function'), + (relfileid + '::SpamTests', 'SpamTests', 'suite'), + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::SpamTests::test_spam[a-[b]-c]', name='test_spam[a-[b]-c]', @@ -645,11 +736,11 @@ def test_nested_suite(self): stub = Stub() discovered = StubDiscoveredTests(stub) session = StubPytestSession(stub) - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = 'x/y/z/test_eggs.py'.replace('/', os.path.sep) + testroot = fix_path('/a/b/c') + relfile = fix_path('x/y/z/test_eggs.py') relfileid = os.path.join('.', relfile) session.items = [ - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile + '::SpamTests::Ham::Eggs::test_spam', name='test_spam', @@ -666,10 +757,15 @@ def test_nested_suite(self): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[ - relfileid + '::SpamTests', - relfileid + '::SpamTests::Ham', - relfileid + '::SpamTests::Ham::Eggs', + parents=[ + (relfileid + '::SpamTests::Ham::Eggs', 'Eggs', 'suite'), + (relfileid + '::SpamTests::Ham', 'Ham', 'suite'), + (relfileid + '::SpamTests', 'SpamTests', 'suite'), + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), ], test=TestInfo( id=relfileid + '::SpamTests::Ham::Eggs::test_spam', @@ -694,7 +790,7 @@ def test_windows(self): testroot = r'c:\a\b\c' relfile = r'X\Y\Z\test_eggs.py' session.items = [ - StubPytestItem( + StubFunctionItem( stub, nodeid='X/Y/Z/test_eggs.py::SpamTests::test_spam', name='test_spam', @@ -717,7 +813,14 @@ def normcase(path): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[r'.\x\y\z\test_eggs.py::SpamTests'], + parents=[ + (r'.\x\y\z\test_eggs.py::SpamTests', 'SpamTests', 'suite'), + (r'.\x\y\z\test_eggs.py', 'test_eggs.py', 'file'), + (r'.\x\y\z', 'z', 'folder'), + (r'.\x\y', 'y', 'folder'), + (r'.\x', 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=r'.\x\y\z\test_eggs.py::SpamTests::test_spam', name='test_spam', @@ -738,11 +841,11 @@ def test_mysterious_parens(self): stub = Stub() discovered = StubDiscoveredTests(stub) session = StubPytestSession(stub) - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = 'x/y/z/test_eggs.py'.replace('/', os.path.sep) + testroot = fix_path('/a/b/c') + relfile = fix_path('x/y/z/test_eggs.py') relfileid = os.path.join('.', relfile) session.items = [ - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile + '::SpamTests::()::()::test_spam', name='test_spam', @@ -759,7 +862,14 @@ def test_mysterious_parens(self): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[relfileid + '::SpamTests'], + parents=[ + (relfileid + '::SpamTests', 'SpamTests', 'suite'), + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::SpamTests::test_spam', name='test_spam', @@ -782,12 +892,12 @@ def test_imported_test(self): stub = Stub() discovered = StubDiscoveredTests(stub) session = StubPytestSession(stub) - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = 'x/y/z/test_eggs.py'.replace('/', os.path.sep) + testroot = fix_path('/a/b/c') + relfile = fix_path('x/y/z/test_eggs.py') relfileid = os.path.join('.', relfile) - srcfile = 'x/y/z/_extern.py'.replace('/', os.path.sep) + srcfile = fix_path('x/y/z/_extern.py') session.items = [ - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile + '::SpamTests::test_spam', name='test_spam', @@ -795,7 +905,7 @@ def test_imported_test(self): fspath=os.path.join(testroot, relfile), function=FakeFunc('test_spam'), ), - StubPytestItem( + StubFunctionItem( stub, nodeid=relfile + '::test_ham', name='test_ham', @@ -812,7 +922,14 @@ def test_imported_test(self): self.assertEqual(stub.calls, [ ('discovered.reset', None, None), ('discovered.add_test', None, dict( - suiteids=[relfileid + '::SpamTests'], + parents=[ + (relfileid + '::SpamTests', 'SpamTests', 'suite'), + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::SpamTests::test_spam', name='test_spam', @@ -828,7 +945,13 @@ def test_imported_test(self): ), )), ('discovered.add_test', None, dict( - suiteids=[], + parents=[ + (relfileid, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], test=TestInfo( id=relfileid + '::test_ham', name='test_ham', @@ -844,534 +967,3 @@ def test_imported_test(self): ), )), ]) - - -class DiscoveredTestsTests(unittest.TestCase): - - def test_list(self): - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = 'test_spam.py' - relfileid = os.path.join('.', relfile) - tests = [ - TestInfo( - id=relfile + '::test_each[10-10]', - name='test_each[10-10]', - path=TestPath( - root=testroot, - relfile=relfile, - func='test_each', - sub=['[10-10]'], - ), - source='{}:{}'.format(relfile, 10), - markers=None, - parentid=relfile + '::test_each', - ), - TestInfo( - id=relfile + '::All::BasicTests::test_first', - name='test_first', - path=TestPath( - root=testroot, - relfile=relfile, - func='All.BasicTests.test_first', - sub=None, - ), - source='{}:{}'.format(relfile, 62), - markers=None, - parentid=relfile + '::All::BasicTests', - ), - ] - allsuiteids = [ - [], - [relfile + '::All', - relfile + '::All::BasicTests', - ], - ] - expected = [test._replace(id=os.path.join('.', test.id), - parentid=os.path.join('.', test.parentid)) - for test in tests] - discovered = DiscoveredTests() - for test, suiteids in zip(tests, allsuiteids): - discovered.add_test(test, suiteids) - size = len(discovered) - items = [discovered[0], discovered[1]] - snapshot = list(discovered) - - self.maxDiff = None - self.assertEqual(size, 2) - self.assertEqual(items, expected) - self.assertEqual(snapshot, expected) - - def test_reset(self): - testroot = '/a/b/c'.replace('/', os.path.sep) - discovered = DiscoveredTests() - discovered.add_test( - TestInfo( - id='test_spam.py::test_each', - name='test_each', - path=TestPath( - root=testroot, - relfile='test_spam.py', - func='test_each', - ), - source='{}:{}'.format('test_spam.py', 11), - markers=[], - parentid='test_spam.py', - ), - []) - - before = len(discovered), len(discovered.parents) - discovered.reset() - after = len(discovered), len(discovered.parents) - - self.assertEqual(before, (1, 2)) - self.assertEqual(after, (0, 0)) - - def test_parents(self): - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = 'x/y/z/test_spam.py'.replace('/', os.path.sep) - relfileid = os.path.join('.', relfile) - tests = [ - TestInfo( - id=relfile + '::test_each[10-10]', - name='test_each[10-10]', - path=TestPath( - root=testroot, - relfile=relfile, - func='test_each', - sub=['[10-10]'], - ), - source='{}:{}'.format(relfile, 10), - markers=None, - parentid=relfile + '::test_each', - ), - TestInfo( - id=relfile + '::All::BasicTests::test_first', - name='test_first', - path=TestPath( - root=testroot, - relfile=relfile, - func='All.BasicTests.test_first', - sub=None, - ), - source='{}:{}'.format(relfile, 61), - markers=None, - parentid=relfile + '::All::BasicTests', - ), - ] - allsuiteids = [ - [], - [relfile + '::All', - relfile + '::All::BasicTests', - ], - ] - discovered = DiscoveredTests() - for test, suiteids in zip(tests, allsuiteids): - discovered.add_test(test, suiteids) - - parents = discovered.parents - - self.maxDiff = None - self.assertEqual(parents, [ - ParentInfo( - id='.', - kind='folder', - name=testroot, - ), - ParentInfo( - id='./x'.replace('/', os.path.sep), - kind='folder', - name='x', - root=testroot, - parentid='.', - ), - ParentInfo( - id='./x/y'.replace('/', os.path.sep), - kind='folder', - name='y', - root=testroot, - parentid='./x'.replace('/', os.path.sep), - ), - ParentInfo( - id='./x/y/z'.replace('/', os.path.sep), - kind='folder', - name='z', - root=testroot, - parentid='./x/y'.replace('/', os.path.sep), - ), - ParentInfo( - id=relfileid, - kind='file', - name=os.path.basename(relfile), - root=testroot, - parentid=os.path.dirname(relfileid), - ), - ParentInfo( - id=relfileid + '::All', - kind='suite', - name='All', - root=testroot, - parentid=relfileid, - ), - ParentInfo( - id=relfileid + '::All::BasicTests', - kind='suite', - name='BasicTests', - root=testroot, - parentid=relfileid + '::All', - ), - ParentInfo( - id=relfileid + '::test_each', - kind='function', - name='test_each', - root=testroot, - parentid=relfileid, - ), - ]) - - def test_add_test_simple(self): - testroot = '/a/b/c'.replace('/', os.path.sep) - test = TestInfo( - id='test_spam.py::test_spam', - name='test_spam', - path=TestPath( - root=testroot, - relfile='test_spam.py', - func='test_spam', - ), - source='{}:{}'.format('test_spam.py', 11), - markers=[], - parentid='test_spam.py', - ) - expected = test._replace(id=os.path.join('.', test.id), - parentid=os.path.join('.', test.parentid)) - discovered = DiscoveredTests() - - before = list(discovered), discovered.parents - discovered.add_test(test, []) - after = list(discovered), discovered.parents - - self.maxDiff = None - self.assertEqual(before, ([], [])) - self.assertEqual(after, ([expected], [ - ParentInfo( - id='.', - kind='folder', - name=testroot, - ), - ParentInfo( - id=os.path.join('.', 'test_spam.py'), - kind='file', - name='test_spam.py', - root=testroot, - parentid='.', - ), - ])) - - def test_multiroot(self): - # the first root - testroot1 = '/a/b/c'.replace('/', os.path.sep) - relfile1 = 'test_spam.py' - relfileid1 = os.path.join('.', relfile1) - alltests = [ - TestInfo( - id=relfile1 + '::test_spam', - name='test_spam', - path=TestPath( - root=testroot1, - relfile=relfile1, - func='test_spam', - ), - source='{}:{}'.format(relfile1, 10), - markers=[], - parentid=relfile1, - ), - ] - allsuiteids = [ - [], - ] - # the second root - testroot2 = '/x/y/z'.replace('/', os.path.sep) - relfile2 = 'w/test_eggs.py' - relfileid2 = os.path.join('.', relfile2) - alltests.extend([ - TestInfo( - id=relfile2 + 'BasicTests::test_first', - name='test_first', - path=TestPath( - root=testroot2, - relfile=relfile2, - func='BasicTests.test_first', - ), - source='{}:{}'.format(relfile2, 61), - markers=[], - parentid=relfile2 + '::BasicTests', - ), - ]) - allsuiteids.extend([ - [relfile2 + '::BasicTests', - ], - ]) - - discovered = DiscoveredTests() - for test, suiteids in zip(alltests, allsuiteids): - discovered.add_test(test, suiteids) - tests = list(discovered) - parents = discovered.parents - - self.maxDiff = None - self.assertEqual(tests, [ - # the first root - TestInfo( - id=relfileid1 + '::test_spam', - name='test_spam', - path=TestPath( - root=testroot1, - relfile=relfile1, - func='test_spam', - ), - source='{}:{}'.format(relfile1, 10), - markers=[], - parentid=relfileid1, - ), - # the secondroot - TestInfo( - id=relfileid2 + 'BasicTests::test_first', - name='test_first', - path=TestPath( - root=testroot2, - relfile=relfile2, - func='BasicTests.test_first', - ), - source='{}:{}'.format(relfile2, 61), - markers=[], - parentid=relfileid2 + '::BasicTests', - ), - ]) - self.assertEqual(parents, [ - # the first root - ParentInfo( - id='.', - kind='folder', - name=testroot1, - ), - ParentInfo( - id=relfileid1, - kind='file', - name=os.path.basename(relfile1), - root=testroot1, - parentid=os.path.dirname(relfileid1), - ), - # the secondroot - ParentInfo( - id='.', - kind='folder', - name=testroot2, - ), - ParentInfo( - id='./w'.replace('/', os.path.sep), - kind='folder', - name='w', - root=testroot2, - parentid='.', - ), - ParentInfo( - id=relfileid2, - kind='file', - name=os.path.basename(relfile2), - root=testroot2, - parentid=os.path.dirname(relfileid2), - ), - ParentInfo( - id=relfileid2 + '::BasicTests', - kind='suite', - name='BasicTests', - root=testroot2, - parentid=relfileid2, - ), - ]) - - def test_doctest(self): - stub = Stub() - testroot = '/a/b/c'.replace('/', os.path.sep) - doctestfile = './x/test_doctest.txt'.replace('/', os.path.sep) - relfile = './x/y/z/test_eggs.py'.replace('/', os.path.sep) - alltests = [ - TestInfo( - id=doctestfile + '::test_doctest.txt', - name='test_doctest.txt', - path=TestPath( - root=testroot, - relfile=doctestfile, - func=None, - ), - source='{}:{}'.format(doctestfile, 0), - markers=[], - parentid=doctestfile, - ), - # With --doctest-modules - TestInfo( - id=relfile + '::test_eggs', - name='test_eggs', - path=TestPath( - root=testroot, - relfile=relfile, - func=None, - ), - source='{}:{}'.format(relfile, 0), - markers=[], - parentid=relfile, - ), - TestInfo( - id=relfile + '::test_eggs.TestSpam', - name='test_eggs.TestSpam', - path=TestPath( - root=testroot, - relfile=relfile, - func=None, - ), - source='{}:{}'.format(relfile, 12), - markers=[], - parentid=relfile, - ), - TestInfo( - id=relfile + '::test_eggs.TestSpam.TestEggs', - name='test_eggs.TestSpam.TestEggs', - path=TestPath( - root=testroot, - relfile=relfile, - func=None, - ), - source='{}:{}'.format(relfile, 27), - markers=[], - parentid=relfile, - ), - ] - discovered = DiscoveredTests() - - for test in alltests: - discovered.add_test(test, []) - tests = list(discovered) - parents = discovered.parents - - self.maxDiff = None - self.assertEqual(tests, alltests) - self.assertEqual(parents, [ - ParentInfo( - id='.', - kind='folder', - name=testroot, - ), - ParentInfo( - id='./x'.replace('/', os.path.sep), - kind='folder', - name='x', - root=testroot, - parentid='.', - ), - ParentInfo( - id=doctestfile, - kind='file', - name=os.path.basename(doctestfile), - root=testroot, - parentid=os.path.dirname(doctestfile), - ), - ParentInfo( - id='./x/y'.replace('/', os.path.sep), - kind='folder', - name='y', - root=testroot, - parentid='./x'.replace('/', os.path.sep), - ), - ParentInfo( - id='./x/y/z'.replace('/', os.path.sep), - kind='folder', - name='z', - root=testroot, - parentid='./x/y'.replace('/', os.path.sep), - ), - ParentInfo( - id=relfile, - kind='file', - name=os.path.basename(relfile), - root=testroot, - parentid=os.path.dirname(relfile), - ), - ]) - - def test_nested_suite_simple(self): - stub = Stub() - discovered = StubDiscoveredTests(stub) - session = StubPytestSession(stub) - testroot = '/a/b/c'.replace('/', os.path.sep) - relfile = './test_eggs.py'.replace('/', os.path.sep) - alltests = [ - TestInfo( - id=relfile + '::TestOuter::TestInner::test_spam', - name='test_spam', - path=TestPath( - root=testroot, - relfile=relfile, - func='TestOuter.TestInner.test_spam', - ), - source='{}:{}'.format(relfile, 10), - markers=None, - parentid=relfile + '::TestOuter::TestInner', - ), - TestInfo( - id=relfile + '::TestOuter::TestInner::test_eggs', - name='test_eggs', - path=TestPath( - root=testroot, - relfile=relfile, - func='TestOuter.TestInner.test_eggs', - ), - source='{}:{}'.format(relfile, 21), - markers=None, - parentid=relfile + '::TestOuter::TestInner', - ), - ] - allsuiteids = [ - [relfile + '::TestOuter', - relfile + '::TestOuter::TestInner', - ], - [relfile + '::TestOuter', - relfile + '::TestOuter::TestInner', - ], - ] - - discovered = DiscoveredTests() - for test, suiteids in zip(alltests, allsuiteids): - discovered.add_test(test, suiteids) - tests = list(discovered) - parents = discovered.parents - - self.maxDiff = None - self.assertEqual(tests, alltests) - self.assertEqual(parents, [ - ParentInfo( - id='.', - kind='folder', - name=testroot, - ), - ParentInfo( - id=relfile, - kind='file', - name=os.path.basename(relfile), - root=testroot, - parentid=os.path.dirname(relfile), - ), - ParentInfo( - id=relfile + '::TestOuter', - kind='suite', - name='TestOuter', - root=testroot, - parentid=relfile, - ), - ParentInfo( - id=relfile + '::TestOuter::TestInner', - kind='suite', - name='TestInner', - root=testroot, - parentid=relfile + '::TestOuter', - ), - ]) diff --git a/pythonFiles/tests/testing_tools/adapter/test_discovery.py b/pythonFiles/tests/testing_tools/adapter/test_discovery.py new file mode 100644 index 000000000000..01475afd4b00 --- /dev/null +++ b/pythonFiles/tests/testing_tools/adapter/test_discovery.py @@ -0,0 +1,595 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from __future__ import absolute_import, print_function + +import os.path +import unittest + +from testing_tools.adapter.info import TestInfo, TestPath, ParentInfo +from testing_tools.adapter.discovery import DiscoveredTests + + +def fix_path(nodeid): + return nodeid.replace('/', os.path.sep) + + +class DiscoveredTestsTests(unittest.TestCase): + + def test_list(self): + testroot = fix_path('/a/b/c') + relfile = 'test_spam.py' + relfileid = os.path.join('.', relfile) + tests = [ + TestInfo( + id=relfile + '::test_each[10-10]', + name='test_each[10-10]', + path=TestPath( + root=testroot, + relfile=relfile, + func='test_each', + sub=['[10-10]'], + ), + source='{}:{}'.format(relfile, 10), + markers=None, + parentid=relfile + '::test_each', + ), + TestInfo( + id=relfile + '::All::BasicTests::test_first', + name='test_first', + path=TestPath( + root=testroot, + relfile=relfile, + func='All.BasicTests.test_first', + sub=None, + ), + source='{}:{}'.format(relfile, 62), + markers=None, + parentid=relfile + '::All::BasicTests', + ), + ] + allparents= [ + [(relfileid + '::test_each', 'test_each', 'function'), + (relfileid, relfile, 'file'), + ('.', testroot, 'folder'), + ], + [(relfileid + '::All::BasicTests', 'BasicTests', 'suite'), + (relfileid + '::All', 'All', 'suite'), + (relfileid, relfile, 'file'), + ('.', testroot, 'folder'), + ], + ] + expected = [test._replace(id=os.path.join('.', test.id), + parentid=os.path.join('.', test.parentid)) + for test in tests] + discovered = DiscoveredTests() + for test, parents in zip(tests, allparents): + discovered.add_test(test, parents) + size = len(discovered) + items = [discovered[0], discovered[1]] + snapshot = list(discovered) + + self.maxDiff = None + self.assertEqual(size, 2) + self.assertEqual(items, expected) + self.assertEqual(snapshot, expected) + + def test_reset(self): + testroot = fix_path('/a/b/c') + discovered = DiscoveredTests() + discovered.add_test( + TestInfo( + id='./test_spam.py::test_each', + name='test_each', + path=TestPath( + root=testroot, + relfile='test_spam.py', + func='test_each', + ), + source='{}:{}'.format('test_spam.py', 11), + markers=[], + parentid='./test_spam.py', + ), + [('./test_spam.py', 'test_spam.py', 'file'), + ('.', testroot, 'folder'), + ]) + + before = len(discovered), len(discovered.parents) + discovered.reset() + after = len(discovered), len(discovered.parents) + + self.assertEqual(before, (1, 2)) + self.assertEqual(after, (0, 0)) + + def test_parents(self): + testroot = fix_path('/a/b/c') + relfile = fix_path('x/y/z/test_spam.py') + relfileid = os.path.join('.', relfile) + tests = [ + TestInfo( + id=relfile + '::test_each[10-10]', + name='test_each[10-10]', + path=TestPath( + root=testroot, + relfile=relfile, + func='test_each', + sub=['[10-10]'], + ), + source='{}:{}'.format(relfile, 10), + markers=None, + parentid=relfile + '::test_each', + ), + TestInfo( + id=relfile + '::All::BasicTests::test_first', + name='test_first', + path=TestPath( + root=testroot, + relfile=relfile, + func='All.BasicTests.test_first', + sub=None, + ), + source='{}:{}'.format(relfile, 61), + markers=None, + parentid=relfile + '::All::BasicTests', + ), + ] + allparents= [ + [(relfileid + '::test_each', 'test_each', 'function'), + (relfileid, relfile, 'file'), + ('.', testroot, 'folder'), + ], + [(relfileid + '::All::BasicTests', 'BasicTests', 'suite'), + (relfileid + '::All', 'All', 'suite'), + (relfileid, 'test_spam.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], + ] + discovered = DiscoveredTests() + for test, parents in zip(tests, allparents): + discovered.add_test(test, parents) + + parents = discovered.parents + + self.maxDiff = None + self.assertEqual(parents, [ + ParentInfo( + id='.', + kind='folder', + name=testroot, + ), + ParentInfo( + id=fix_path('./x'), + kind='folder', + name='x', + root=testroot, + parentid='.', + ), + ParentInfo( + id=fix_path('./x/y'), + kind='folder', + name='y', + root=testroot, + parentid=fix_path('./x'), + ), + ParentInfo( + id=fix_path('./x/y/z'), + kind='folder', + name='z', + root=testroot, + parentid=fix_path('./x/y'), + ), + ParentInfo( + id=relfileid, + kind='file', + name=os.path.basename(relfile), + root=testroot, + parentid=os.path.dirname(relfileid), + ), + ParentInfo( + id=relfileid + '::All', + kind='suite', + name='All', + root=testroot, + parentid=relfileid, + ), + ParentInfo( + id=relfileid + '::All::BasicTests', + kind='suite', + name='BasicTests', + root=testroot, + parentid=relfileid + '::All', + ), + ParentInfo( + id=relfileid + '::test_each', + kind='function', + name='test_each', + root=testroot, + parentid=relfileid, + ), + ]) + + def test_add_test_simple(self): + testroot = fix_path('/a/b/c') + relfile = 'test_spam.py' + relfileid = os.path.join('.', relfile) + test = TestInfo( + id=relfile + '::test_spam', + name='test_spam', + path=TestPath( + root=testroot, + relfile=relfile, + func='test_spam', + ), + source='{}:{}'.format(relfile, 11), + markers=[], + parentid=relfile, + ) + expected = test._replace(id=os.path.join('.', test.id), + parentid=relfileid) + discovered = DiscoveredTests() + + before = list(discovered), discovered.parents + discovered.add_test(test, [ + (relfile, relfile, 'file'), + ('.', testroot, 'folder'), + ]) + after = list(discovered), discovered.parents + + self.maxDiff = None + self.assertEqual(before, ([], [])) + self.assertEqual(after, ([expected], [ + ParentInfo( + id='.', + kind='folder', + name=testroot, + ), + ParentInfo( + id=relfileid, + kind='file', + name=relfile, + root=testroot, + parentid='.', + ), + ])) + + def test_multiroot(self): + # the first root + testroot1 = fix_path('/a/b/c') + relfile1 = 'test_spam.py' + relfileid1 = os.path.join('.', relfile1) + alltests = [ + TestInfo( + id=relfile1 + '::test_spam', + name='test_spam', + path=TestPath( + root=testroot1, + relfile=relfile1, + func='test_spam', + ), + source='{}:{}'.format(relfile1, 10), + markers=[], + parentid=relfile1, + ), + ] + allparents = [ + [(relfileid1, 'test_spam.py', 'file'), + ('.', testroot1, 'folder'), + ], + ] + # the second root + testroot2 = fix_path('/x/y/z') + relfile2 = 'w/test_eggs.py' + relfileid2 = os.path.join('.', relfile2) + alltests.extend([ + TestInfo( + id=relfile2 + 'BasicTests::test_first', + name='test_first', + path=TestPath( + root=testroot2, + relfile=relfile2, + func='BasicTests.test_first', + ), + source='{}:{}'.format(relfile2, 61), + markers=[], + parentid=relfile2 + '::BasicTests', + ), + ]) + allparents.extend([ + [(relfileid2 + '::BasicTests', 'BasicTests', 'suite'), + (relfileid2, 'test_eggs.py', 'file'), + (fix_path('./w'), 'w', 'folder'), + ('.', testroot2, 'folder'), + ], + ]) + + discovered = DiscoveredTests() + for test, parents in zip(alltests, allparents): + discovered.add_test(test, parents) + tests = list(discovered) + parents = discovered.parents + + self.maxDiff = None + self.assertEqual(tests, [ + # the first root + TestInfo( + id=relfileid1 + '::test_spam', + name='test_spam', + path=TestPath( + root=testroot1, + relfile=relfile1, + func='test_spam', + ), + source='{}:{}'.format(relfile1, 10), + markers=[], + parentid=relfileid1, + ), + # the secondroot + TestInfo( + id=relfileid2 + 'BasicTests::test_first', + name='test_first', + path=TestPath( + root=testroot2, + relfile=relfile2, + func='BasicTests.test_first', + ), + source='{}:{}'.format(relfile2, 61), + markers=[], + parentid=relfileid2 + '::BasicTests', + ), + ]) + self.assertEqual(parents, [ + # the first root + ParentInfo( + id='.', + kind='folder', + name=testroot1, + ), + ParentInfo( + id=relfileid1, + kind='file', + name=os.path.basename(relfile1), + root=testroot1, + parentid=os.path.dirname(relfileid1), + ), + # the secondroot + ParentInfo( + id='.', + kind='folder', + name=testroot2, + ), + ParentInfo( + id=fix_path('./w'), + kind='folder', + name='w', + root=testroot2, + parentid='.', + ), + ParentInfo( + id=relfileid2, + kind='file', + name=os.path.basename(relfile2), + root=testroot2, + parentid=os.path.dirname(relfileid2), + ), + ParentInfo( + id=relfileid2 + '::BasicTests', + kind='suite', + name='BasicTests', + root=testroot2, + parentid=relfileid2, + ), + ]) + + def test_doctest(self): + testroot = fix_path('/a/b/c') + doctestfile = fix_path('./x/test_doctest.txt') + relfile = fix_path('./x/y/z/test_eggs.py') + alltests = [ + TestInfo( + id=doctestfile + '::test_doctest.txt', + name='test_doctest.txt', + path=TestPath( + root=testroot, + relfile=doctestfile, + func=None, + ), + source='{}:{}'.format(doctestfile, 0), + markers=[], + parentid=doctestfile, + ), + # With --doctest-modules + TestInfo( + id=relfile + '::test_eggs', + name='test_eggs', + path=TestPath( + root=testroot, + relfile=relfile, + func=None, + ), + source='{}:{}'.format(relfile, 0), + markers=[], + parentid=relfile, + ), + TestInfo( + id=relfile + '::test_eggs.TestSpam', + name='test_eggs.TestSpam', + path=TestPath( + root=testroot, + relfile=relfile, + func=None, + ), + source='{}:{}'.format(relfile, 12), + markers=[], + parentid=relfile, + ), + TestInfo( + id=relfile + '::test_eggs.TestSpam.TestEggs', + name='test_eggs.TestSpam.TestEggs', + path=TestPath( + root=testroot, + relfile=relfile, + func=None, + ), + source='{}:{}'.format(relfile, 27), + markers=[], + parentid=relfile, + ), + ] + allparents = [ + [(doctestfile, 'test_doctest.txt', 'file'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], + [(relfile, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], + [(relfile, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], + [(relfile, 'test_eggs.py', 'file'), + (fix_path('./x/y/z'), 'z', 'folder'), + (fix_path('./x/y'), 'y', 'folder'), + (fix_path('./x'), 'x', 'folder'), + ('.', testroot, 'folder'), + ], + ] + + discovered = DiscoveredTests() + + for test, parents in zip(alltests, allparents): + discovered.add_test(test, parents) + tests = list(discovered) + parents = discovered.parents + + self.maxDiff = None + self.assertEqual(tests, alltests) + self.assertEqual(parents, [ + ParentInfo( + id='.', + kind='folder', + name=testroot, + ), + ParentInfo( + id=fix_path('./x'), + kind='folder', + name='x', + root=testroot, + parentid='.', + ), + ParentInfo( + id=doctestfile, + kind='file', + name=os.path.basename(doctestfile), + root=testroot, + parentid=os.path.dirname(doctestfile), + ), + ParentInfo( + id=fix_path('./x/y'), + kind='folder', + name='y', + root=testroot, + parentid=fix_path('./x'), + ), + ParentInfo( + id=fix_path('./x/y/z'), + kind='folder', + name='z', + root=testroot, + parentid=fix_path('./x/y'), + ), + ParentInfo( + id=relfile, + kind='file', + name=os.path.basename(relfile), + root=testroot, + parentid=os.path.dirname(relfile), + ), + ]) + + def test_nested_suite_simple(self): + testroot = fix_path('/a/b/c') + relfile = fix_path('./test_eggs.py') + alltests = [ + TestInfo( + id=relfile + '::TestOuter::TestInner::test_spam', + name='test_spam', + path=TestPath( + root=testroot, + relfile=relfile, + func='TestOuter.TestInner.test_spam', + ), + source='{}:{}'.format(relfile, 10), + markers=None, + parentid=relfile + '::TestOuter::TestInner', + ), + TestInfo( + id=relfile + '::TestOuter::TestInner::test_eggs', + name='test_eggs', + path=TestPath( + root=testroot, + relfile=relfile, + func='TestOuter.TestInner.test_eggs', + ), + source='{}:{}'.format(relfile, 21), + markers=None, + parentid=relfile + '::TestOuter::TestInner', + ), + ] + allparents= [ + [(relfile + '::TestOuter::TestInner', 'TestInner', 'suite'), + (relfile + '::TestOuter', 'TestOuter', 'suite'), + (relfile, 'test_eggs.py', 'file'), + ('.', testroot, 'folder'), + ], + [(relfile + '::TestOuter::TestInner', 'TestInner', 'suite'), + (relfile + '::TestOuter', 'TestOuter', 'suite'), + (relfile, 'test_eggs.py', 'file'), + ('.', testroot, 'folder'), + ], + ] + + discovered = DiscoveredTests() + for test, parents in zip(alltests, allparents): + discovered.add_test(test, parents) + tests = list(discovered) + parents = discovered.parents + + self.maxDiff = None + self.assertEqual(tests, alltests) + self.assertEqual(parents, [ + ParentInfo( + id='.', + kind='folder', + name=testroot, + ), + ParentInfo( + id=relfile, + kind='file', + name=os.path.basename(relfile), + root=testroot, + parentid=os.path.dirname(relfile), + ), + ParentInfo( + id=relfile + '::TestOuter', + kind='suite', + name='TestOuter', + root=testroot, + parentid=relfile, + ), + ParentInfo( + id=relfile + '::TestOuter::TestInner', + kind='suite', + name='TestInner', + root=testroot, + parentid=relfile + '::TestOuter', + ), + ]) diff --git a/pythonFiles/tests/testing_tools/adapter/test_functional.py b/pythonFiles/tests/testing_tools/adapter/test_functional.py index c1a8a4597643..c5be00c6bc4a 100644 --- a/pythonFiles/tests/testing_tools/adapter/test_functional.py +++ b/pythonFiles/tests/testing_tools/adapter/test_functional.py @@ -21,7 +21,8 @@ def resolve_testroot(name): - projroot = os.path.join(DATA_DIR, name) + projroot = os.path.normcase( + os.path.join(DATA_DIR, name)) return projroot, os.path.join(projroot, 'tests')