# Source code for nose2.plugins.testid

    """
    Allow easy test selection with test ids.

    Assigns (and, in verbose mode, prints) a sequential test id for each
    test executed. Ids can be fed back in as test names, and this plugin
    will translate them back to full test names. Saves typing!

    This plugin implements :func:`reportStartTest`,
    :func:`loadTestsFromName`, :func:`loadTestsFromNames` and
    :func:`stopTest`.

    """

    from __future__ import annotations

    import os
    import pickle
    import re

    from nose2 import util
    from nose2.events import Plugin

    __unittest = True

    class TestId(Plugin):
        """Allow easy test select with ids"""

        configSection = "testid"
        commandLineSwitch = ("I", "with-id", "Add test ids to output")
        idpat = re.compile(r"(\d+)")

        def __init__(self) -> None:
            self.idfile = self.config.as_str("id-file", ".noseids")
            self.ids: dict[str, str] = {}
            self.tests: dict[str, str] = {}
            if not os.path.isabs(self.idfile):
                # FIXME expand-user?
                self.idfile = os.path.join(os.getcwd(), self.idfile)
            self.id = 0
            self._loaded = False

        def nextId(self):
            """Increment ID and return it."""
            self.id += 1
            return self.id

        def reportStartTest(self, event):
            """Record and possibly output test id"""
            testid = util.test_name(event.testEvent.test)
            if testid not in self.tests:
                id_ = self.nextId()
                self.ids[id_] = testid
                self.tests[testid] = id_
            else:
                id_ = self.tests[testid]
            event.metadata["testid"] = id_
            if self.session.verbosity > 1:
                event.stream.write("#%s " % id_)

        def loadTestsFromName(self, event):
            """Load tests from a name that is an id

            If the name is a number, it might be an ID assigned by us. If we can
            find a test to which we have assigned that ID, event.name is changed to
            the test's real ID. In this way, tests can be referred to via sequential
            numbers.
            """
            testid = self._testNameFromId(event.name)
            if testid is not None:
                event.name = testid

        def loadTestsFromNames(self, event):
            """Translate test ids into test names"""
            for i, name in enumerate(event.names[:]):
                testid = self._testNameFromId(name)
                if testid is not None:
                    event.names[i] = testid

        def stopTestRun(self, event):
            """Write testids file"""
            with open(self.idfile, "wb") as fh:
                pickle.dump({"ids": self.ids, "tests": self.tests}, fh)

        def loadIds(self):
            """Load previously pickled 'ids' and 'tests' attributes."""
            if self._loaded:
                return

            try:
                with open(self.idfile, "rb") as fh:
                    data = pickle.load(fh)
            except OSError:
                self._loaded = True
                return

            if "ids" in data:
                self.ids = data["ids"]
            if "tests" in data:
                self.tests = data["tests"]
            self.id = max(self.ids.keys())
            self._loaded = True

        def _testNameFromId(self, name):
            """Try to translate one of our IDs to real test ID."""
            m = self.idpat.match(name)
            if m is None:
                return None

            id_ = int(m.groups()[0])

            self.loadIds()
            # Translate to test's real ID
            try:
                return self.ids[id_]
            except KeyError:
                return None