blob: 001421f211b6b32a31c29b9b8c4a471e3fa63368 [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
Pau Espin Pedrolef919c02020-06-05 13:19:02 +020024import pathlib
Neels Hofmeyr3531a192017-03-28 14:30:28 +020025
Pau Espin Pedrolf574a462020-05-05 12:18:35 +020026from . import log
27from . import util
28from . import report
Pau Espin Pedrolee217b02020-05-04 19:06:47 +020029from . import suite
Neels Hofmeyr3531a192017-03-28 14:30:28 +020030
31FILE_MARK_TAKEN = 'taken'
32FILE_CHECKSUMS = 'checksums.md5'
33TIMESTAMP_FMT = '%Y-%m-%d_%H-%M-%S'
34FILE_LAST_RUN = 'last_run'
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020035FILE_LOG = 'log'
Neels Hofmeyr4f33dcc2017-05-07 00:01:09 +020036FILE_LOG_BRIEF = 'log_brief'
Neels Hofmeyr3531a192017-03-28 14:30:28 +020037
38class Trial(log.Origin):
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020039 UNKNOWN = 'UNKNOWN'
40 PASS = 'PASS'
41 FAIL = 'FAIL'
42
Neels Hofmeyr3531a192017-03-28 14:30:28 +020043 @staticmethod
44 def next(trials_dir):
45
46 with trials_dir.lock('Trial.next'):
47 trials = [e for e in trials_dir.children()
48 if trials_dir.isdir(e) and not trials_dir.exists(e, FILE_MARK_TAKEN)]
49 if not trials:
50 return None
51 # sort by time to get the one that waited longest
52 trials.sort(key=lambda e: os.path.getmtime(trials_dir.child(e)))
53 next_trial = trials[0]
54 return Trial(trials_dir.child(next_trial)).take()
55
56 def __init__(self, trial_dir):
Neels Hofmeyre44a0cb2017-05-14 03:25:45 +020057 self.path = os.path.abspath(trial_dir)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020058 super().__init__(log.C_TST, os.path.basename(self.path))
Neels Hofmeyr3531a192017-03-28 14:30:28 +020059 self.dir = util.Dir(self.path)
60 self.inst_dir = util.Dir(self.dir.child('inst'))
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +020061 self.bin_tars = {}
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020062 self.suites = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020063 self.status = Trial.UNKNOWN
Pau Espin Pedrol58603672018-08-09 13:45:55 +020064 self._run_dir = None
65 self.log_targets = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020066
67 def __repr__(self):
68 return self.name()
69
70 def __enter__(self):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020071 '''add a log target to log to the run dir, write taken marker, log a
72 starting separator.'''
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020073 run_dir = self.get_run_dir()
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020074 detailed_log = run_dir.new_child(FILE_LOG)
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020075 self.log_targets = [
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020076 log.FileLogTarget(detailed_log)
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020077 .set_all_levels(log.L_DBG)
78 .style_change(trace=True),
Neels Hofmeyr4f33dcc2017-05-07 00:01:09 +020079 log.FileLogTarget(run_dir.new_child(FILE_LOG_BRIEF))
Neels Hofmeyr9576f5f2017-05-24 18:31:01 +020080 .style_change(src=False, all_origins_on_levels=(log.L_ERR, log.L_TRACEBACK))
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020081 ]
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020082 log.large_separator(self.name(), sublevel=1)
Neels Hofmeyr39b0b892017-05-14 16:16:31 +020083 self.log('Detailed log at', detailed_log)
Neels Hofmeyr506edbc2017-05-06 21:56:27 +020084 self.take()
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020085 return self
Neels Hofmeyr3531a192017-03-28 14:30:28 +020086
87 def __exit__(self, *exc_info):
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020088 '''log a report, then remove log file targets for this trial'''
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020089 self.log_report()
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020090 for lt in self.log_targets:
91 lt.remove()
92 self.log_targets = None
93
Neels Hofmeyr3531a192017-03-28 14:30:28 +020094 def take(self):
95 self.dir.touch(FILE_MARK_TAKEN)
96 return self
97
98 def get_run_dir(self):
99 if self._run_dir is not None:
100 return self._run_dir
101 self._run_dir = util.Dir(self.dir.new_child('run.%s' % time.strftime(TIMESTAMP_FMT)))
102 self._run_dir.mkdir()
103
104 last_run = self.dir.child(FILE_LAST_RUN)
105 if os.path.islink(last_run):
106 os.remove(last_run)
107 if not os.path.exists(last_run):
108 os.symlink(self.dir.rel_path(self._run_dir.path), last_run)
109 return self._run_dir
110
111 def verify(self):
112 "verify checksums"
113
114 if not self.dir.exists():
115 raise RuntimeError('Trial dir does not exist: %r' % self.dir)
116 if not self.dir.isdir():
117 raise RuntimeError('Trial dir is not a dir: %r' % self.dir)
118
119 checksums = self.dir.child(FILE_CHECKSUMS)
120 if not self.dir.isfile(FILE_CHECKSUMS):
121 raise RuntimeError('No checksums file in trial dir: %r', checksums)
122
123 with open(checksums, 'r') as f:
124 line_nr = 0
125 for line in [l.strip() for l in f.readlines()]:
126 line_nr += 1
127 if not line:
128 continue
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200129 md5, relpath = line.split(' ')
130 file_path = self.dir.child(relpath)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200131
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200132 if not self.dir.isfile(relpath):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200133 raise RuntimeError('File listed in checksums file but missing in trials dir:'
134 ' %r vs. %r line %d' % (file_path, checksums, line_nr))
135
136 if md5 != util.md5_of_file(file_path):
137 raise RuntimeError('Checksum mismatch for %r vs. %r line %d'
138 % (file_path, checksums, line_nr))
139
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200140 if relpath.endswith('.tgz') or relpath.endswith('.tar.gz'):
141 (label, name) = os.path.split(relpath)
142 #print('label: %s, name: %s' % (label, name))
143 li = self.bin_tars.get(label, [])
144 li.append(name)
145 self.bin_tars[label] = li
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200146
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200147 def has_bin_tar(self, bin_name, run_label):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200148 bin_tar_start = '%s.' % bin_name
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200149 matches = [t for t in self.bin_tars[run_label] if t.startswith(bin_tar_start)]
150 self.dbg('has bin_tar', run_label=run_label, bin_name=bin_name, matches=matches)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200151 if not matches:
152 return None
153 if len(matches) > 1:
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200154 raise RuntimeError('More than one match for bin name %r on run_label \'%s\': %r' % (bin_name, run_label, matches))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200155 bin_tar = matches[0]
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200156 bin_tar_path = self.dir.child(os.path.join(run_label, bin_tar))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200157 if not os.path.isfile(bin_tar_path):
158 raise RuntimeError('Not a file or missing: %r' % bin_tar_path)
159 return bin_tar_path
160
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200161 def get_inst(self, bin_name, run_label=None):
162 if run_label is None:
163 run_label = ''
164 bin_tar = self.has_bin_tar(bin_name, run_label)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200165 if not bin_tar:
Your Name3c6673a2017-04-08 18:52:39 +0200166 raise RuntimeError('No such binary available: %r' % bin_name)
Pau Espin Pedrol6e0b6fb2020-05-25 19:49:29 +0200167 inst_dir = self.inst_dir.child(os.path.join(run_label, bin_name))
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200168
169 if os.path.isdir(inst_dir):
170 # already unpacked
171 return inst_dir
172
173 t = None
174 try:
175 os.makedirs(inst_dir)
176 t = tarfile.open(bin_tar)
177 t.extractall(inst_dir)
178 return inst_dir
179
180 except:
181 shutil.rmtree(inst_dir)
182 raise
183 finally:
184 if t:
185 try:
186 t.close()
187 except:
188 pass
189
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200190 def add_suite_run(self, suite_scenario_str, suite_def, scenarios):
191 suite_run = suite.SuiteRun(self, suite_scenario_str, suite_def, scenarios)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200192 self.suites.append(suite_run)
193
194 def run_suites(self, names=None):
195 self.status = Trial.UNKNOWN
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200196 try:
197 for suite_run in self.suites:
198 try:
199 suite_run.run_tests(names)
200 except BaseException as e:
201 # when the program is aborted by a signal (like Ctrl-C), escalate to abort all.
202 self.err('TRIAL RUN ABORTED: %s' % type(e).__name__)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200203 # log the traceback before the trial's logging is ended
204 log.log_exn()
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200205 raise
206 finally:
207 if suite_run.status != suite.SuiteRun.PASS:
208 self.status = Trial.FAIL
209 if self.status == Trial.UNKNOWN:
210 self.status = Trial.PASS
211 finally:
212 junit_path = self.get_run_dir().new_file(self.name()+'.xml')
213 self.log('Storing JUnit report in', junit_path)
214 report.trial_to_junit_write(self, junit_path)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200215
Pau Espin Pedrolef919c02020-06-05 13:19:02 +0200216 def get_all_inst_hash_info(self):
217 d = {}
218 pathlist = pathlib.Path(str(self.inst_dir)).glob('**/*git_hashes.txt')
219 for path in pathlist:
220 # because path is object not string
221 abs_path_str = str(path) # because path is object not string
222 dir, file = os.path.split(abs_path_str)
223 reldir = os.path.relpath(dir, str(self.inst_dir)).rstrip(os.sep)
224 with open(abs_path_str, 'r') as f:
225 for line in [l.strip() for l in f.readlines()]:
226 if not line:
227 continue
228 hash, proj = tuple(line.split(' ', 1))
229 d[os.path.join(reldir,proj)] = hash
230 return d
231
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200232 def log_report(self):
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200233 log.large_separator(self.name(), self.status)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200234 self.log(report.trial_to_text(self))
235
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200236# vim: expandtab tabstop=4 shiftwidth=4