blob: 741f3a7db260be18165c6a4dc20e4e34f7afef09 [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)
Your Name3c6673a2017-04-08 18:52:39 +020060 self.set_name(os.path.basename(self.path))
Neels Hofmeyr3531a192017-03-28 14:30:28 +020061 self.set_log_category(log.C_TST)
62 self.dir = util.Dir(self.path)
63 self.inst_dir = util.Dir(self.dir.child('inst'))
64 self.bin_tars = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020065 self.suites = []
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +020066 self.status = Trial.UNKNOWN
Neels Hofmeyr3531a192017-03-28 14:30:28 +020067
68 def __repr__(self):
69 return self.name()
70
71 def __enter__(self):
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020072 # add a log target to log to the run dir
73 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 Hofmeyr3531a192017-03-28 14:30:28 +020085 super().__enter__()
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020086 return self
Neels Hofmeyr3531a192017-03-28 14:30:28 +020087
88 def __exit__(self, *exc_info):
89 super().__exit__(*exc_info)
Neels Hofmeyr6ccda112017-06-06 19:41:17 +020090 self.log_report()
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020091 for lt in self.log_targets:
92 lt.remove()
93 self.log_targets = None
94
Neels Hofmeyr3531a192017-03-28 14:30:28 +020095 def take(self):
96 self.dir.touch(FILE_MARK_TAKEN)
97 return self
98
99 def get_run_dir(self):
100 if self._run_dir is not None:
101 return self._run_dir
102 self._run_dir = util.Dir(self.dir.new_child('run.%s' % time.strftime(TIMESTAMP_FMT)))
103 self._run_dir.mkdir()
104
105 last_run = self.dir.child(FILE_LAST_RUN)
106 if os.path.islink(last_run):
107 os.remove(last_run)
108 if not os.path.exists(last_run):
109 os.symlink(self.dir.rel_path(self._run_dir.path), last_run)
110 return self._run_dir
111
112 def verify(self):
113 "verify checksums"
114
115 if not self.dir.exists():
116 raise RuntimeError('Trial dir does not exist: %r' % self.dir)
117 if not self.dir.isdir():
118 raise RuntimeError('Trial dir is not a dir: %r' % self.dir)
119
120 checksums = self.dir.child(FILE_CHECKSUMS)
121 if not self.dir.isfile(FILE_CHECKSUMS):
122 raise RuntimeError('No checksums file in trial dir: %r', checksums)
123
124 with open(checksums, 'r') as f:
125 line_nr = 0
126 for line in [l.strip() for l in f.readlines()]:
127 line_nr += 1
128 if not line:
129 continue
130 md5, filename = line.split(' ')
131 file_path = self.dir.child(filename)
132
133 if not self.dir.isfile(filename):
134 raise RuntimeError('File listed in checksums file but missing in trials dir:'
135 ' %r vs. %r line %d' % (file_path, checksums, line_nr))
136
137 if md5 != util.md5_of_file(file_path):
138 raise RuntimeError('Checksum mismatch for %r vs. %r line %d'
139 % (file_path, checksums, line_nr))
140
141 if filename.endswith('.tgz'):
142 self.bin_tars.append(filename)
143
144 def has_bin_tar(self, bin_name):
145 bin_tar_start = '%s.' % bin_name
146 matches = [t for t in self.bin_tars if t.startswith(bin_tar_start)]
147 self.dbg(bin_name=bin_name, matches=matches)
148 if not matches:
149 return None
150 if len(matches) > 1:
151 raise RuntimeError('More than one match for bin name %r: %r' % (bin_name, matches))
152 bin_tar = matches[0]
153 bin_tar_path = self.dir.child(bin_tar)
154 if not os.path.isfile(bin_tar_path):
155 raise RuntimeError('Not a file or missing: %r' % bin_tar_path)
156 return bin_tar_path
157
158 def get_inst(self, bin_name):
159 bin_tar = self.has_bin_tar(bin_name)
160 if not bin_tar:
Your Name3c6673a2017-04-08 18:52:39 +0200161 raise RuntimeError('No such binary available: %r' % bin_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200162 inst_dir = self.inst_dir.child(bin_name)
163
164 if os.path.isdir(inst_dir):
165 # already unpacked
166 return inst_dir
167
168 t = None
169 try:
170 os.makedirs(inst_dir)
171 t = tarfile.open(bin_tar)
172 t.extractall(inst_dir)
173 return inst_dir
174
175 except:
176 shutil.rmtree(inst_dir)
177 raise
178 finally:
179 if t:
180 try:
181 t.close()
182 except:
183 pass
184
Neels Hofmeyr6ccda112017-06-06 19:41:17 +0200185 def add_suite_run(self, suite_scenario_str, suite_def, scenarios):
186 suite_run = suite.SuiteRun(self, suite_scenario_str, suite_def, scenarios)
Pau Espin Pedrol0ffb4142017-05-15 18:24:35 +0200187 self.suites.append(suite_run)
188
189 def run_suites(self, names=None):
190 self.status = Trial.UNKNOWN
Neels Hofmeyrf8e61862017-06-06 23:08:07 +0200191 try:
192 for suite_run in self.suites:
193 try:
194 suite_run.run_tests(names)
195 except BaseException as e:
196 # when the program is aborted by a signal (like Ctrl-C), escalate to abort all.
197 self.err('TRIAL RUN ABORTED: %s' % type(e).__name__)
198 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