Skip to content

Commit 50dec2f

Browse files
committed
Support importing results from other DB files
1 parent 92aa97c commit 50dec2f

File tree

3 files changed

+121
-37
lines changed

3 files changed

+121
-37
lines changed

reframe/frontend/cli.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,11 +1048,23 @@ def restrict_logging():
10481048
if spec['import']['from'] == 'perflog':
10491049
kwargs = spec['import']
10501050
del kwargs['from']
1051-
report = reporting.RunReport.create_from_perflog(*options.args,
1052-
**kwargs)
1053-
# report.save('foo.json', link_to_last=False)
1054-
uuid = report.store()
1055-
printer.info(f'Results imported successfully as session {uuid}')
1051+
reports = reporting.RunReport.create_from_perflog(
1052+
*options.args, **kwargs
1053+
)
1054+
elif spec['import']['from'] == 'sqlite':
1055+
kwargs = spec['import']
1056+
del kwargs['from']
1057+
reports = reporting.RunReport.create_from_sqlite_db(
1058+
*options.args, **kwargs
1059+
)
1060+
1061+
for rpt in reports:
1062+
uuid = rpt.store()
1063+
printer.info(f'Successfully imported session {uuid}')
1064+
1065+
if not reports:
1066+
printer.info('No sessions have been imported')
1067+
10561068
sys.exit(0)
10571069

10581070
# Show configuration after everything is set up

reframe/frontend/reporting/__init__.py

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# SPDX-License-Identifier: BSD-3-Clause
55

66
import decimal
7+
import collections
78
import functools
89
import inspect
910
import json
@@ -246,8 +247,8 @@ class RunReport:
246247
'''
247248
def __init__(self):
248249
# Initialize the report with the required fields
249-
self.__filename = None
250-
self.__report = {
250+
self._filename = None
251+
self._report = {
251252
'session_info': {
252253
'data_version': DATA_VERSION,
253254
'hostname': socket.gethostname(),
@@ -261,16 +262,16 @@ def __init__(self):
261262

262263
@property
263264
def filename(self):
264-
return self.__filename
265+
return self._filename
265266

266267
def __getattr__(self, name):
267-
return getattr(self.__report, name)
268+
return getattr(self._report, name)
268269

269270
def __getitem__(self, key):
270-
return self.__report[key]
271+
return self._report[key]
271272

272273
def __rfm_json_encode__(self):
273-
return self.__report
274+
return self._report
274275

275276
@classmethod
276277
def create_from_perflog(cls, *logfiles, format=None,
@@ -393,23 +394,60 @@ def _convert(x):
393394
'run_index': run_index,
394395
'testcases': testcases
395396
})
396-
return report
397+
return [report]
398+
399+
@classmethod
400+
def create_from_sqlite_db(cls, *dbfiles, exclude_sessions=None,
401+
include_sessions=None, time_period=None):
402+
dst_backend = StorageBackend.default()
403+
dst_schema = dst_backend.schema_version()
404+
if not time_period:
405+
time_period = {'start': '19700101T0000+0000', 'end': 'now'}
406+
407+
start = time_period.get('start', '19700101T0000+0000')
408+
end = time_period.get('end', 'now')
409+
ts_start, ts_end = parse_time_period(f'{start}:{end}')
410+
include_sessions = set(include_sessions) if include_sessions else set()
411+
exclude_sessions = set(exclude_sessions) if exclude_sessions else set()
412+
reports = []
413+
for filename in dbfiles:
414+
src_backend = StorageBackend.create('sqlite', filename)
415+
src_schema = src_backend.schema_version()
416+
if src_schema != dst_schema:
417+
getlogger().warning(
418+
f'ignoring DB file {filename}: schema version mismatch: '
419+
f'cannot import from DB v{src_schema} to v{dst_schema}'
420+
)
421+
continue
422+
423+
sessions = src_backend.fetch_sessions_time_period(ts_start, ts_end)
424+
for sess in sessions:
425+
uuid = sess['session_info']['uuid']
426+
if include_sessions and uuid not in include_sessions:
427+
continue
428+
429+
if exclude_sessions and uuid in exclude_sessions:
430+
continue
431+
432+
reports.append(_ImportedRunReport(sess))
433+
434+
return reports
397435

398436
def _add_run(self, run):
399-
self.__report['runs'].append(run)
437+
self._report['runs'].append(run)
400438

401439
def update_session_info(self, session_info):
402440
# Remove timestamps
403441
for key, val in session_info.items():
404442
if not key.startswith('time_'):
405-
self.__report['session_info'][key] = val
443+
self._report['session_info'][key] = val
406444

407445
def update_restored_cases(self, restored_cases, restored_session):
408-
self.__report['restored_cases'] = [restored_session.case(c)
409-
for c in restored_cases]
446+
self._report['restored_cases'] = [restored_session.case(c)
447+
for c in restored_cases]
410448

411449
def update_timestamps(self, ts_start, ts_end):
412-
self.__report['session_info'].update({
450+
self._report['session_info'].update({
413451
'time_start': time.strftime(_DATETIME_FMT,
414452
time.localtime(ts_start)),
415453
'time_start_unix': ts_start,
@@ -426,10 +464,10 @@ def update_extras(self, extras):
426464
raise ValueError('cannot use reserved keys '
427465
f'`{",".join(clashed_keys)}` as session extras')
428466

429-
self.__report['session_info'].update(extras)
467+
self._report['session_info'].update(extras)
430468

431469
def update_run_stats(self, stats):
432-
session_uuid = self.__report['session_info']['uuid']
470+
session_uuid = self._report['session_info']['uuid']
433471
for runidx, tasks in stats.runs():
434472
testcases = []
435473
num_failures = 0
@@ -530,7 +568,7 @@ def update_run_stats(self, stats):
530568

531569
testcases.append(entry)
532570

533-
self.__report['runs'].append({
571+
self._report['runs'].append({
534572
'num_cases': len(tasks),
535573
'num_failures': num_failures,
536574
'num_aborted': num_aborted,
@@ -540,23 +578,23 @@ def update_run_stats(self, stats):
540578
})
541579

542580
# Update session info from stats
543-
self.__report['session_info'].update({
544-
'num_cases': self.__report['runs'][0]['num_cases'],
545-
'num_failures': self.__report['runs'][-1]['num_failures'],
546-
'num_aborted': self.__report['runs'][-1]['num_aborted'],
547-
'num_skipped': self.__report['runs'][-1]['num_skipped']
581+
self._report['session_info'].update({
582+
'num_cases': self._report['runs'][0]['num_cases'],
583+
'num_failures': self._report['runs'][-1]['num_failures'],
584+
'num_aborted': self._report['runs'][-1]['num_aborted'],
585+
'num_skipped': self._report['runs'][-1]['num_skipped']
548586
})
549587

550588
def _save(self, filename, compress, link_to_last):
551589
filename = _expand_report_filename(filename, newfile=True)
552590
with open(filename, 'w') as fp:
553591
if compress:
554-
jsonext.dump(self.__report, fp)
592+
jsonext.dump(self._report, fp)
555593
else:
556-
jsonext.dump(self.__report, fp, indent=2)
594+
jsonext.dump(self._report, fp, indent=2)
557595
fp.write('\n')
558596

559-
self.__filename = filename
597+
self._filename = filename
560598
if not link_to_last:
561599
return
562600

@@ -576,7 +614,7 @@ def _save(self, filename, compress, link_to_last):
576614

577615
def is_empty(self):
578616
'''Return :obj:`True` is no test cases where run'''
579-
return self.__report['session_info']['num_cases'] == 0
617+
return self._report['session_info']['num_cases'] == 0
580618

581619
def save(self, filename, compress=False, link_to_last=True):
582620
prefix = os.path.dirname(filename) or '.'
@@ -591,7 +629,7 @@ def store(self):
591629
def generate_xml_report(self):
592630
'''Generate a JUnit report from a standard ReFrame JSON report.'''
593631

594-
report = self.__report
632+
report = self._report
595633
xml_testsuites = etree.Element('testsuites')
596634
# Create a XSD-friendly timestamp
597635
session_ts = time.strftime(
@@ -688,6 +726,30 @@ def __missing__(self, key):
688726
raise KeyError(key)
689727

690728

729+
class _ImportedRunReport(RunReport):
730+
def __init__(self, report):
731+
self._filename = f'{report["session_info"]["uuid"]}.json'
732+
self._report = report
733+
734+
def _add_run(self, run):
735+
raise NotImplementedError
736+
737+
def update_session_info(self, session_info):
738+
raise NotImplementedError
739+
740+
def update_restored_cases(self, restored_cases, restored_session):
741+
raise NotImplementedError
742+
743+
def update_timestamps(self, ts_start, ts_end):
744+
raise NotImplementedError
745+
746+
def update_extras(self, extras):
747+
raise NotImplementedError
748+
749+
def update_run_stats(self, stats):
750+
raise NotImplementedError
751+
752+
691753
def _group_key(groups, testcase: _TCProxy):
692754
key = []
693755
for grp in groups:

reframe/frontend/reporting/storage.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ def remove_sessions(self, selector: QuerySelector):
7373

7474

7575
class _SqliteStorage(StorageBackend):
76-
SCHEMA_VERSION = '1.0'
76+
_SCHEMA_VERSION = '1.0'
7777

78-
def __init__(self):
79-
self.__db_file = os.path.join(
80-
osext.expandvars(runtime().get_option('storage/0/sqlite_db_file'))
78+
def __init__(self, dbfile=None):
79+
self.__db_file = dbfile or osext.expandvars(
80+
runtime().get_option('storage/0/sqlite_db_file')
8181
)
8282
mode = runtime().get_option(
8383
'storage/0/sqlite_db_file_mode'
@@ -87,6 +87,16 @@ def __init__(self):
8787
else:
8888
self.__db_file_mode = mode
8989

90+
def schema_version(self):
91+
with self._db_connect(self._db_file()) as conn:
92+
result = conn.execute(
93+
'SELECT schema_version FROM metadata LIMIT 1'
94+
).fetchone()
95+
if not result:
96+
raise ReframeError(f'no DB metadata found in {self.__db_file}')
97+
98+
return result[0]
99+
90100
def _db_file(self):
91101
prefix = os.path.dirname(self.__db_file)
92102
if not os.path.exists(self.__db_file):
@@ -172,14 +182,14 @@ def _db_schema_check(self):
172182
# DB is new, insert the schema version
173183
with self._db_connect(self.__db_file) as conn:
174184
conn.execute('INSERT INTO metadata VALUES(:schema_version)',
175-
{'schema_version': self.SCHEMA_VERSION})
185+
{'schema_version': self._SCHEMA_VERSION})
176186
else:
177187
found_ver = results[0][0]
178-
if found_ver != self.SCHEMA_VERSION:
188+
if found_ver != self._SCHEMA_VERSION:
179189
raise ReframeError(
180190
f'results DB in {self.__db_file!r} is '
181191
'of incompatible version: '
182-
f'found {found_ver}, required: {self.SCHEMA_VERSION}'
192+
f'found {found_ver}, required: {self._SCHEMA_VERSION}'
183193
)
184194

185195
def _db_store_report(self, conn, report, report_file_path):

0 commit comments

Comments
 (0)