diff --git a/stestr/repository/abstract.py b/stestr/repository/abstract.py index 94471b20..7e3c1219 100644 --- a/stestr/repository/abstract.py +++ b/stestr/repository/abstract.py @@ -67,7 +67,7 @@ def get_failing(self): """ raise NotImplementedError(self.get_failing) - def get_inserter(self, partial=False, run_id=None): + def get_inserter(self, partial=False, run_id=None, metadata=None): """Get an inserter that will insert a test run into the repository. Repository implementations should implement _get_inserter. @@ -85,7 +85,7 @@ def get_inserter(self, partial=False, run_id=None): """ return self._get_inserter(partial, run_id) - def _get_inserter(self, partial=False, run_id=None): + def _get_inserter(self, partial=False, run_id=None, metadata=None): """Get an inserter for get_inserter. The result is decorated with an AutoTimingTestResultDecorator. @@ -156,6 +156,14 @@ def gather(test_dict): result.stopTestRun() return ids + def find_metadata(self, metadata): + """Return the list of run_ids for a given metadata string. + + :param: metadata: the metadata string to search for. + :return: a list of any test_ids that have that metadata value. + """ + raise NotImplementedError(self.find_metadata) + class AbstractTestRun(object): """A test run that has been stored in a repository. @@ -186,6 +194,13 @@ def get_test(self): """ raise NotImplementedError(self.get_test) + def get_metadata(self): + """Get the metadata value for the test run. + + :return: A string of the metadata or None if it doesn't exist. + """ + raise NotImplementedError(self.get_metadata) + class RepositoryNotFound(Exception): """Raised when we try to open a repository that isn't there.""" diff --git a/stestr/repository/file.py b/stestr/repository/file.py index 56611f41..4633bc66 100644 --- a/stestr/repository/file.py +++ b/stestr/repository/file.py @@ -115,6 +115,17 @@ def get_failing(self): raise return _DiskRun(None, run_subunit_content) + def _get_metadata(self, run_id): + db = my_dbm.open(self._path('meta.dbm'), 'c') + try: + try: + metadata = db[run_id] + except KeyError: + metadata = None + finally: + db.close() + return metadata + def get_test_run(self, run_id): try: with open(os.path.join(self.base, str(run_id)), 'rb') as fp: @@ -124,7 +135,8 @@ def get_test_run(self, run_id): raise KeyError("No such run.") else: raise - return _DiskRun(run_id, run_subunit_content) + metadata = self._get_metadata(run_id) + return _DiskRun(run_id, run_subunit_content, metadata=metadata) def _get_inserter(self, partial, run_id=None): return _Inserter(self, partial, run_id) @@ -167,15 +179,27 @@ def _write_next_stream(self, value): stream.write('%d\n' % value) atomicish_rename(prefix + '.new', prefix) + def find_metadata(self, metadata): + run_ids = [] + db = my_dbm.open(self._path('meta.dbm'), 'c') + try: + for run_id in db: + if db[run_id] == metadata: + run_ids.append(run_id) + finally: + db.close() + return run_ids + class _DiskRun(repository.AbstractTestRun): """A test run that was inserted into the repository.""" - def __init__(self, run_id, subunit_content): + def __init__(self, run_id, subunit_content, metadata=None): """Create a _DiskRun with the content subunit_content.""" self._run_id = run_id self._content = subunit_content assert type(subunit_content) is bytes + self._metadata = metadata def get_id(self): return self._run_id @@ -211,14 +235,18 @@ def wrap_result(result): case, wrap_result, methodcaller('startTestRun'), methodcaller('stopTestRun')) + def get_metadata(self): + return self._metadata + class _SafeInserter(object): - def __init__(self, repository, partial=False, run_id=None): + def __init__(self, repository, partial=False, run_id=None, metadata=None): # XXX: Perhaps should factor into a decorator and use an unaltered # TestProtocolClient. self._repository = repository self._run_id = run_id + self._metadata = metadata if not self._run_id: fd, name = tempfile.mkstemp(dir=self._repository.base) self.fname = name @@ -226,6 +254,12 @@ def __init__(self, repository, partial=False, run_id=None): else: self.fname = os.path.join(self._repository.base, self._run_id) stream = open(self.fname, 'ab') + if self._metadata: + db = my_dbm.open(self._repository._path('meta.dbm'), 'c') + try: + db[self._run_id] = self._metadata + finally: + db.close() self.partial = partial # The time take by each test, flushed at the end. self._times = {} diff --git a/stestr/repository/memory.py b/stestr/repository/memory.py index 4ccd9aa6..0f067203 100644 --- a/stestr/repository/memory.py +++ b/stestr/repository/memory.py @@ -127,13 +127,14 @@ def run(self, result): class _Inserter(repository.AbstractTestRun): """Insert test results into a memory repository.""" - def __init__(self, repository, partial, run_id=None): + def __init__(self, repository, partial, run_id=None, metadata=None): self._repository = repository self._partial = partial self._tests = [] # Subunit V2 stream for get_subunit_stream self._subunit = None self._run_id = run_id + self._metadata = metadata def startTestRun(self): self._subunit = BytesIO() diff --git a/stestr/repository/sql.py b/stestr/repository/sql.py index 36f5bfb7..7455df43 100644 --- a/stestr/repository/sql.py +++ b/stestr/repository/sql.py @@ -116,8 +116,8 @@ def get_failing(self): def get_test_run(self, run_id): return _Subunit2SqlRun(self.base, run_id) - def _get_inserter(self, partial, run_id=None): - return _SqlInserter(self, partial, run_id) + def _get_inserter(self, partial, run_id=None, metadata=None): + return _SqlInserter(self, partial, run_id, metadata) def _get_test_times(self, test_ids): result = {} @@ -136,6 +136,12 @@ def _get_test_times(self, test_ids): session.close() return result + def find_metadata(self, metadata): + session = self.session_factory() + runs = db_api.get_runs_by_key_value('stestr_run_meta', metadata, + session=session) + return [x.uuid for x in runs] + class _Subunit2SqlRun(repository.AbstractTestRun): """A test run that was inserted into the repository.""" @@ -177,15 +183,25 @@ def get_test(self): case = subunit.ByteStreamToStreamResult(stream) return case + def get_metdata(self): + if self._run_id: + session = self.session_factory() + metadata = db_api.get_run_metadata(self._run_id, session=session) + for meta in metadata: + if meta.key == 'stestr_run_meta': + return meta.value + return None + class _SqlInserter(repository.AbstractTestRun): """Insert test results into a sql repository.""" - def __init__(self, repository, partial=False, run_id=None): + def __init__(self, repository, partial=False, run_id=None, metadata=None): self._repository = repository self.partial = partial self._subunit = None self._run_id = run_id + self._metadata = metadata # Create a new session factory self.engine = sqlalchemy.create_engine(self._repository.base) self.session_factory = orm.sessionmaker(bind=self.engine, @@ -202,6 +218,9 @@ def startTestRun(self): session = self.session_factory() if not self._run_id: self.run = db_api.create_run(session=session) + if self._metadata: + db_api.add_run_metadata({'stestr_run_meta': self._metadata}, + self.run.id, session=session) self._run_id = self.run.uuid else: int_id = db_api.get_run_id_from_uuid(self._run_id, session=session)