blob: 9dcc18809eb9c6483e8e84cc2c2716c04fa599bb [file] [log] [blame]
Neels Hofmeyr3531a192017-03-28 14:30:28 +02001# osmo_gsm_tester: trial: directory of binaries to be tested
2#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Neels Hofmeyr <neels@hofmeyr.de>
6#
7# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +02008# it under the terms of the GNU General Public License as
Neels Hofmeyr3531a192017-03-28 14:30:28 +02009# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte27205342017-06-03 09:51:45 +020015# GNU General Public License for more details.
Neels Hofmeyr3531a192017-03-28 14:30:28 +020016#
Harald Welte27205342017-06-03 09:51:45 +020017# You should have received a copy of the GNU General Public License
Neels Hofmeyr3531a192017-03-28 14:30:28 +020018# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import os
21import time
22import shutil
23import tarfile
24
Pau Espin Pedrole8bbcbf2020-04-10 19:51:31 +020025from .core import log, util
26from . import suite, report
Neels Hofmeyr3531a192017-03-28 14:30:28 +020027
28FILE_MARK_TAKEN = 'taken'
29FILE_CHECKSUMS = 'checksums.md5'
30TIMESTAMP_FMT = '%Y-%m-%d_%H-%M-%S'
31FILE_LAST_RUN = 'last_run'
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020032FILE_LOG = 'log'
Neels Hofmeyr4f33dcc2017-05-07 00:01:09 +020033FILE_LOG_BRIEF = 'log_brief'
Neels Hofmeyr3531a192017-03-28 14:30:28 +020034
35class Trial(log.Origin):
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020036 UNKNOWN = 'UNKNOWN'
37 PASS = 'PASS'
38 FAIL = 'FAIL'
39
Neels Hofmeyr3531a192017-03-28 14:30:28 +020040 @staticmethod
41 def next(trials_dir):
42
43 with trials_dir.lock('Trial.next'):
44 trials = [e for e in trials_dir.children()
45 if trials_dir.isdir(e) and not trials_dir.exists(e, FILE_MARK_TAKEN)]
46 if not trials:
47 return None
48 # sort by time to get the one that waited longest
49 trials.sort(key=lambda e: os.path.getmtime(trials_dir.child(e)))
50 next_trial = trials[0]
51 return Trial(trials_dir.child(next_trial)).take()
52
53 def __init__(self, trial_dir):
Neels Hofmeyre44a0cb2017-05-14 03:25:45 +020054 self.path = os.path.abspath(trial_dir)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020055 super().__init__(log.C_TST, os.path.basename(self.path))
Neels Hofmeyr3531a192017-03-28 14:30:28 +020056 self.dir = util.Dir(self.path)
57 self.inst_dir = util.Dir(self.dir.child('inst'))
58 self.bin_tars = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020059 self.suites = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020060 self.status = Trial.UNKNOWN
Pau Espin Pedrol58603672018-08-09 13:45:55 +020061 self._run_dir = None
62 self.log_targets = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020063
64 def __repr__(self):
65 return self.name()
66
67 def __enter__(self):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020068 '''add a log target to log to the run dir, write taken marker, log a
69 starting separator.'''
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020070 run_dir = self.get_run_dir()
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020071 detailed_log = run_dir.new_child(FILE_LOG)
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020072 self.log_targets = [
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020073 log.FileLogTarget(detailed_log)
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020074 .set_all_levels(log.L_DBG)
75 .style_change(trace=True),
Neels Hofmeyr4f33dcc2017-05-07 00:01:09 +020076 log.FileLogTarget(run_dir.new_child(FILE_LOG_BRIEF))
Neels Hofmeyr9576f5f2017-05-24 18:31:01 +020077 .style_change(src=False, all_origins_on_levels=(log.L_ERR, log.L_TRACEBACK))
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020078 ]
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020079 log.large_separator(self.name(), sublevel=1)
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020080 self.log('Detailed log at', detailed_log)
Neels Hofmeyr506edbc2017-05-06 21:56:27 +020081 self.take()
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020082 return self
Neels Hofmeyr3531a192017-03-28 14:30:28 +020083
84 def __exit__(self, *exc_info):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020085 '''log a report, then remove log file targets for this trial'''
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020086 self.log_report()
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020087 for lt in self.log_targets:
88 lt.remove()
89 self.log_targets = None
90
Neels Hofmeyr3531a192017-03-28 14:30:28 +020091 def take(self):
92 self.dir.touch(FILE_MARK_TAKEN)
93 return self
94
95 def get_run_dir(self):
96 if self._run_dir is not None:
97 return self._run_dir
98 self._run_dir = util.Dir(self.dir.new_child('run.%s' % time.strftime(TIMESTAMP_FMT)))
99 self._run_dir.mkdir()
100
101 last_run = self.dir.child(FILE_LAST_RUN)
102 if os.path.islink(last_run):
103 os.remove(last_run)
104 if not os.path.exists(last_run):
105 os.symlink(self.dir.rel_path(self._run_dir.path), last_run)
106 return self._run_dir
107
108 def verify(self):
109 "verify checksums"
110
111 if not self.dir.exists():
112 raise RuntimeError('Trial dir does not exist: %r' % self.dir)
113 if not self.dir.isdir():
114 raise RuntimeError('Trial dir is not a dir: %r' % self.dir)
115
116 checksums = self.dir.child(FILE_CHECKSUMS)
117 if not self.dir.isfile(FILE_CHECKSUMS):
118 raise RuntimeError('No checksums file in trial dir: %r', checksums)
119
120 with open(checksums, 'r') as f:
121 line_nr = 0
122 for line in [l.strip() for l in f.readlines()]:
123 line_nr += 1
124 if not line:
125 continue
126 md5, filename = line.split(' ')
127 file_path = self.dir.child(filename)
128
129 if not self.dir.isfile(filename):
130 raise RuntimeError('File listed in checksums file but missing in trials dir:'
131 ' %r vs. %r line %d' % (file_path, checksums, line_nr))
132
133 if md5 != util.md5_of_file(file_path):
134 raise RuntimeError('Checksum mismatch for %r vs. %r line %d'
135 % (file_path, checksums, line_nr))
136
137 if filename.endswith('.tgz'):
138 self.bin_tars.append(filename)
139
140 def has_bin_tar(self, bin_name):
141 bin_tar_start = '%s.' % bin_name
142 matches = [t for t in self.bin_tars if t.startswith(bin_tar_start)]
143 self.dbg(bin_name=bin_name, matches=matches)
144 if not matches:
145 return None
146 if len(matches) > 1:
147 raise RuntimeError('More than one match for bin name %r: %r' % (bin_name, matches))
148 bin_tar = matches[0]
149 bin_tar_path = self.dir.child(bin_tar)
150 if not os.path.isfile(bin_tar_path):
151 raise RuntimeError('Not a file or missing: %r' % bin_tar_path)
152 return bin_tar_path
153
154 def get_inst(self, bin_name):
155 bin_tar = self.has_bin_tar(bin_name)
156 if not bin_tar:
Your Name3c6673a2017-04-08 18:52:39 +0200157 raise RuntimeError('No such binary available: %r' % bin_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200158 inst_dir = self.inst_dir.child(bin_name)
159
160 if os.path.isdir(inst_dir):
161 # already unpacked
162 return inst_dir
163
164 t = None
165 try:
166 os.makedirs(inst_dir)
167 t = tarfile.open(bin_tar)
168 t.extractall(inst_dir)
169 return inst_dir
170
171 except:
172 shutil.rmtree(inst_dir)
173 raise
174 finally:
175 if t:
176 try:
177 t.close()
178 except:
179 pass
180
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200181 def add_suite_run(self, suite_scenario_str, suite_def, scenarios):
182 suite_run = suite.SuiteRun(self, suite_scenario_str, suite_def, scenarios)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200183 self.suites.append(suite_run)
184
185 def run_suites(self, names=None):
186 self.status = Trial.UNKNOWN
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200187 try:
188 for suite_run in self.suites:
189 try:
190 suite_run.run_tests(names)
191 except BaseException as e:
192 # when the program is aborted by a signal (like Ctrl-C), escalate to abort all.
193 self.err('TRIAL RUN ABORTED: %s' % type(e).__name__)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200194 # log the traceback before the trial's logging is ended
195 log.log_exn()
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200196 raise
197 finally:
198 if suite_run.status != suite.SuiteRun.PASS:
199 self.status = Trial.FAIL
200 if self.status == Trial.UNKNOWN:
201 self.status = Trial.PASS
202 finally:
203 junit_path = self.get_run_dir().new_file(self.name()+'.xml')
204 self.log('Storing JUnit report in', junit_path)
205 report.trial_to_junit_write(self, junit_path)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200206
207 def log_report(self):
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200208 log.large_separator(self.name(), self.status)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200209 self.log(report.trial_to_text(self))
210
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200211# vim: expandtab tabstop=4 shiftwidth=4