blob: 83b0f115ea07e14a433b01c75d05664645ec1048 [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 Pedrolf574a462020-05-05 12:18:35 +020025from . import log
26from . import util
27from . import report
Pau Espin Pedrolee217b02020-05-04 19:06:47 +020028from . import suite
Neels Hofmeyr3531a192017-03-28 14:30:28 +020029
30FILE_MARK_TAKEN = 'taken'
31FILE_CHECKSUMS = 'checksums.md5'
32TIMESTAMP_FMT = '%Y-%m-%d_%H-%M-%S'
33FILE_LAST_RUN = 'last_run'
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020034FILE_LOG = 'log'
Neels Hofmeyr4f33dcc2017-05-07 00:01:09 +020035FILE_LOG_BRIEF = 'log_brief'
Neels Hofmeyr3531a192017-03-28 14:30:28 +020036
37class Trial(log.Origin):
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020038 UNKNOWN = 'UNKNOWN'
39 PASS = 'PASS'
40 FAIL = 'FAIL'
41
Neels Hofmeyr3531a192017-03-28 14:30:28 +020042 @staticmethod
43 def next(trials_dir):
44
45 with trials_dir.lock('Trial.next'):
46 trials = [e for e in trials_dir.children()
47 if trials_dir.isdir(e) and not trials_dir.exists(e, FILE_MARK_TAKEN)]
48 if not trials:
49 return None
50 # sort by time to get the one that waited longest
51 trials.sort(key=lambda e: os.path.getmtime(trials_dir.child(e)))
52 next_trial = trials[0]
53 return Trial(trials_dir.child(next_trial)).take()
54
55 def __init__(self, trial_dir):
Neels Hofmeyre44a0cb2017-05-14 03:25:45 +020056 self.path = os.path.abspath(trial_dir)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020057 super().__init__(log.C_TST, os.path.basename(self.path))
Neels Hofmeyr3531a192017-03-28 14:30:28 +020058 self.dir = util.Dir(self.path)
59 self.inst_dir = util.Dir(self.dir.child('inst'))
60 self.bin_tars = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020061 self.suites = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020062 self.status = Trial.UNKNOWN
Pau Espin Pedrol58603672018-08-09 13:45:55 +020063 self._run_dir = None
64 self.log_targets = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020065
66 def __repr__(self):
67 return self.name()
68
69 def __enter__(self):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020070 '''add a log target to log to the run dir, write taken marker, log a
71 starting separator.'''
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020072 run_dir = self.get_run_dir()
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020073 detailed_log = run_dir.new_child(FILE_LOG)
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020074 self.log_targets = [
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020075 log.FileLogTarget(detailed_log)
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020076 .set_all_levels(log.L_DBG)
77 .style_change(trace=True),
Neels Hofmeyr4f33dcc2017-05-07 00:01:09 +020078 log.FileLogTarget(run_dir.new_child(FILE_LOG_BRIEF))
Neels Hofmeyr9576f5f2017-05-24 18:31:01 +020079 .style_change(src=False, all_origins_on_levels=(log.L_ERR, log.L_TRACEBACK))
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020080 ]
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020081 log.large_separator(self.name(), sublevel=1)
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020082 self.log('Detailed log at', detailed_log)
Neels Hofmeyr506edbc2017-05-06 21:56:27 +020083 self.take()
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020084 return self
Neels Hofmeyr3531a192017-03-28 14:30:28 +020085
86 def __exit__(self, *exc_info):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020087 '''log a report, then remove log file targets for this trial'''
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020088 self.log_report()
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020089 for lt in self.log_targets:
90 lt.remove()
91 self.log_targets = None
92
Neels Hofmeyr3531a192017-03-28 14:30:28 +020093 def take(self):
94 self.dir.touch(FILE_MARK_TAKEN)
95 return self
96
97 def get_run_dir(self):
98 if self._run_dir is not None:
99 return self._run_dir
100 self._run_dir = util.Dir(self.dir.new_child('run.%s' % time.strftime(TIMESTAMP_FMT)))
101 self._run_dir.mkdir()
102
103 last_run = self.dir.child(FILE_LAST_RUN)
104 if os.path.islink(last_run):
105 os.remove(last_run)
106 if not os.path.exists(last_run):
107 os.symlink(self.dir.rel_path(self._run_dir.path), last_run)
108 return self._run_dir
109
110 def verify(self):
111 "verify checksums"
112
113 if not self.dir.exists():
114 raise RuntimeError('Trial dir does not exist: %r' % self.dir)
115 if not self.dir.isdir():
116 raise RuntimeError('Trial dir is not a dir: %r' % self.dir)
117
118 checksums = self.dir.child(FILE_CHECKSUMS)
119 if not self.dir.isfile(FILE_CHECKSUMS):
120 raise RuntimeError('No checksums file in trial dir: %r', checksums)
121
122 with open(checksums, 'r') as f:
123 line_nr = 0
124 for line in [l.strip() for l in f.readlines()]:
125 line_nr += 1
126 if not line:
127 continue
128 md5, filename = line.split(' ')
129 file_path = self.dir.child(filename)
130
131 if not self.dir.isfile(filename):
132 raise RuntimeError('File listed in checksums file but missing in trials dir:'
133 ' %r vs. %r line %d' % (file_path, checksums, line_nr))
134
135 if md5 != util.md5_of_file(file_path):
136 raise RuntimeError('Checksum mismatch for %r vs. %r line %d'
137 % (file_path, checksums, line_nr))
138
139 if filename.endswith('.tgz'):
140 self.bin_tars.append(filename)
141
142 def has_bin_tar(self, bin_name):
143 bin_tar_start = '%s.' % bin_name
144 matches = [t for t in self.bin_tars if t.startswith(bin_tar_start)]
145 self.dbg(bin_name=bin_name, matches=matches)
146 if not matches:
147 return None
148 if len(matches) > 1:
149 raise RuntimeError('More than one match for bin name %r: %r' % (bin_name, matches))
150 bin_tar = matches[0]
151 bin_tar_path = self.dir.child(bin_tar)
152 if not os.path.isfile(bin_tar_path):
153 raise RuntimeError('Not a file or missing: %r' % bin_tar_path)
154 return bin_tar_path
155
156 def get_inst(self, bin_name):
157 bin_tar = self.has_bin_tar(bin_name)
158 if not bin_tar:
Your Name3c6673a2017-04-08 18:52:39 +0200159 raise RuntimeError('No such binary available: %r' % bin_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200160 inst_dir = self.inst_dir.child(bin_name)
161
162 if os.path.isdir(inst_dir):
163 # already unpacked
164 return inst_dir
165
166 t = None
167 try:
168 os.makedirs(inst_dir)
169 t = tarfile.open(bin_tar)
170 t.extractall(inst_dir)
171 return inst_dir
172
173 except:
174 shutil.rmtree(inst_dir)
175 raise
176 finally:
177 if t:
178 try:
179 t.close()
180 except:
181 pass
182
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200183 def add_suite_run(self, suite_scenario_str, suite_def, scenarios):
184 suite_run = suite.SuiteRun(self, suite_scenario_str, suite_def, scenarios)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200185 self.suites.append(suite_run)
186
187 def run_suites(self, names=None):
188 self.status = Trial.UNKNOWN
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200189 try:
190 for suite_run in self.suites:
191 try:
192 suite_run.run_tests(names)
193 except BaseException as e:
194 # when the program is aborted by a signal (like Ctrl-C), escalate to abort all.
195 self.err('TRIAL RUN ABORTED: %s' % type(e).__name__)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200196 # log the traceback before the trial's logging is ended
197 log.log_exn()
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200198 raise
199 finally:
200 if suite_run.status != suite.SuiteRun.PASS:
201 self.status = Trial.FAIL
202 if self.status == Trial.UNKNOWN:
203 self.status = Trial.PASS
204 finally:
205 junit_path = self.get_run_dir().new_file(self.name()+'.xml')
206 self.log('Storing JUnit report in', junit_path)
207 report.trial_to_junit_write(self, junit_path)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200208
209 def log_report(self):
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200210 log.large_separator(self.name(), self.status)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200211 self.log(report.trial_to_text(self))
212
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200213# vim: expandtab tabstop=4 shiftwidth=4