blob: d54e96cd3b0bc022a8eba2186a4412e22d3605dd [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 Pedrol0ffb4142017-05-15 18:24:35 +020025from . import log, util, suite, report
Neels Hofmeyr3531a192017-03-28 14:30:28 +020026
27FILE_MARK_TAKEN = 'taken'
28FILE_CHECKSUMS = 'checksums.md5'
29TIMESTAMP_FMT = '%Y-%m-%d_%H-%M-%S'
30FILE_LAST_RUN = 'last_run'
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020031FILE_LOG = 'log'
Neels Hofmeyr4f33dcc2017-05-07 00:01:09 +020032FILE_LOG_BRIEF = 'log_brief'
Neels Hofmeyr3531a192017-03-28 14:30:28 +020033
34class Trial(log.Origin):
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020035 UNKNOWN = 'UNKNOWN'
36 PASS = 'PASS'
37 FAIL = 'FAIL'
38
Neels Hofmeyr3531a192017-03-28 14:30:28 +020039 path = None
40 dir = None
41 _run_dir = None
42 bin_tars = None
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020043 log_targets = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020044
45 @staticmethod
46 def next(trials_dir):
47
48 with trials_dir.lock('Trial.next'):
49 trials = [e for e in trials_dir.children()
50 if trials_dir.isdir(e) and not trials_dir.exists(e, FILE_MARK_TAKEN)]
51 if not trials:
52 return None
53 # sort by time to get the one that waited longest
54 trials.sort(key=lambda e: os.path.getmtime(trials_dir.child(e)))
55 next_trial = trials[0]
56 return Trial(trials_dir.child(next_trial)).take()
57
58 def __init__(self, trial_dir):
Neels Hofmeyre44a0cb2017-05-14 03:25:45 +020059 self.path = os.path.abspath(trial_dir)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +020060 super().__init__(log.C_TST, os.path.basename(self.path))
Neels Hofmeyr3531a192017-03-28 14:30:28 +020061 self.dir = util.Dir(self.path)
62 self.inst_dir = util.Dir(self.dir.child('inst'))
63 self.bin_tars = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020064 self.suites = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020065 self.status = Trial.UNKNOWN
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
129 md5, filename = line.split(' ')
130 file_path = self.dir.child(filename)
131
132 if not self.dir.isfile(filename):
133 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
140 if filename.endswith('.tgz'):
141 self.bin_tars.append(filename)
142
143 def has_bin_tar(self, bin_name):
144 bin_tar_start = '%s.' % bin_name
145 matches = [t for t in self.bin_tars if t.startswith(bin_tar_start)]
146 self.dbg(bin_name=bin_name, matches=matches)
147 if not matches:
148 return None
149 if len(matches) > 1:
150 raise RuntimeError('More than one match for bin name %r: %r' % (bin_name, matches))
151 bin_tar = matches[0]
152 bin_tar_path = self.dir.child(bin_tar)
153 if not os.path.isfile(bin_tar_path):
154 raise RuntimeError('Not a file or missing: %r' % bin_tar_path)
155 return bin_tar_path
156
157 def get_inst(self, bin_name):
158 bin_tar = self.has_bin_tar(bin_name)
159 if not bin_tar:
Your Name3c6673a2017-04-08 18:52:39 +0200160 raise RuntimeError('No such binary available: %r' % bin_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200161 inst_dir = self.inst_dir.child(bin_name)
162
163 if os.path.isdir(inst_dir):
164 # already unpacked
165 return inst_dir
166
167 t = None
168 try:
169 os.makedirs(inst_dir)
170 t = tarfile.open(bin_tar)
171 t.extractall(inst_dir)
172 return inst_dir
173
174 except:
175 shutil.rmtree(inst_dir)
176 raise
177 finally:
178 if t:
179 try:
180 t.close()
181 except:
182 pass
183
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200184 def add_suite_run(self, suite_scenario_str, suite_def, scenarios):
185 suite_run = suite.SuiteRun(self, suite_scenario_str, suite_def, scenarios)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200186 self.suites.append(suite_run)
187
188 def run_suites(self, names=None):
189 self.status = Trial.UNKNOWN
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200190 try:
191 for suite_run in self.suites:
192 try:
193 suite_run.run_tests(names)
194 except BaseException as e:
195 # when the program is aborted by a signal (like Ctrl-C), escalate to abort all.
196 self.err('TRIAL RUN ABORTED: %s' % type(e).__name__)
Neels Hofmeyr1a7a3f02017-06-10 01:18:27 +0200197 # log the traceback before the trial's logging is ended
198 log.log_exn()
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200199 raise
200 finally:
201 if suite_run.status != suite.SuiteRun.PASS:
202 self.status = Trial.FAIL
203 if self.status == Trial.UNKNOWN:
204 self.status = Trial.PASS
205 finally:
206 junit_path = self.get_run_dir().new_file(self.name()+'.xml')
207 self.log('Storing JUnit report in', junit_path)
208 report.trial_to_junit_write(self, junit_path)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200209
210 def log_report(self):
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200211 log.large_separator(self.name(), self.status)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200212 self.log(report.trial_to_text(self))
213
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200214# vim: expandtab tabstop=4 shiftwidth=4