Repository: pytest-dev/pytest
File: src/_pytest/main.py (Python)

Code before:
                            or node.name.split("[")[0] == matchparts[0]
                        )
                    if is_match:
                        work.append((node, matchparts[1:]))
                        any_matched_in_collector = True

                if not any_matched_in_collector:
                    notfound_collectors.append(matchnode)

            if not any_matched_in_initial_part:
                report_arg = "::".join((str(argpath), *names))
                self._notfound.append((report_arg, notfound_collectors))

            self.trace.root.indent -= 1

    def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
        import inspect

        from _pytest.python import Class
        from _pytest.python import Function
        from _pytest.python import Module

        self.trace("genitems", node)
        if isinstance(node, nodes.Item):
            node.ihook.pytest_itemcollected(item=node)
            if not self.config.getini("collect_imported_tests"):
                if isinstance(node.parent, Module) and isinstance(node, Function):
                    if inspect.isfunction(node._getobj()):
                        fn_defined_at = node._getobj().__module__
                        in_module = node.parent._getobj().__name__
                        if fn_defined_at != in_module:
                            return
            yield node
        else:
            assert isinstance(node, nodes.Collector)
            keepduplicates = self.config.getoption("keepduplicates")
            # For backward compat, dedup only applies to files.
            handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
            rep, duplicate = self._collect_one_node(node, handle_dupes)

            if not self.config.getini("collect_imported_tests"):
                for subnode in rep.result:
                    if isinstance(subnode, Class) and isinstance(
                        subnode.parent, Module
                    ):
                        if inspect.isclass(subnode._getobj()):
                            class_defined_at = subnode._getobj().__module__
                            in_module = subnode.parent._getobj().__name__
                            if class_defined_at != in_module:
                                rep.result.remove(subnode)


Review comment (bug):
Previously this code was placed in `_pytest.python`, which I think is the correct place... as it is implemented here, we are calling `pytest_itemcollected` but then discarding the item if the option is set and the item is not defined in its module, which I think is incorrect: `pytest_itemcollected` should only really be called if we did collect the item.

In fact, it is important for this to be tested too: the following collection hooks should not receive any item that was discarded due to `collect_imported_tests`:

* `pytest_itemcollected`
* `pytest_collectreport`
* `pytest_collection_modifyitems`

Code after:
                            or node.name.split("[")[0] == matchparts[0]
                        )
                    if is_match:
                        work.append((node, matchparts[1:]))
                        any_matched_in_collector = True

                if not any_matched_in_collector:
                    notfound_collectors.append(matchnode)

            if not any_matched_in_initial_part:
                report_arg = "::".join((str(argpath), *names))
                self._notfound.append((report_arg, notfound_collectors))

            self.trace.root.indent -= 1

    def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
        self.trace("genitems", node)
        if isinstance(node, nodes.Item):
            node.ihook.pytest_itemcollected(item=node)
            yield node
        else:
            assert isinstance(node, nodes.Collector)
            keepduplicates = self.config.getoption("keepduplicates")
            # For backward compat, dedup only applies to files.
            handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
            rep, duplicate = self._collect_one_node(node, handle_dupes)
            if duplicate and not keepduplicates:
                return
            if rep.passed:
                for subnode in rep.result:
                    yield from self.genitems(subnode)
            if not duplicate:
                node.ihook.pytest_collectreport(report=rep)


def search_pypath(module_name: str) -> str | None:
    """Search sys.path for the given a dotted module name, and return its file
    system path if found."""
    try:
        spec = importlib.util.find_spec(module_name)
    # AttributeError: looks like package module, but actually filename
    # ImportError: module does not exist
    # ValueError: not a module name
    except (AttributeError, ImportError, ValueError):
        return None
    if spec is None or spec.origin is None or spec.origin == "namespace":
        return None
    elif spec.submodule_search_locations:
        return os.path.dirname(spec.origin)
    else:
        return spec.origin