blob: ae7c5873e9f5b24dcf170422010c139a92527923 [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
8# it under the terms of the GNU Affero General Public License as
9# 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
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import os
21import time
22import shutil
23import tarfile
24
25from . import log, util
26
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 Hofmeyr3531a192017-03-28 14:30:28 +020032
33class Trial(log.Origin):
34 path = None
35 dir = None
36 _run_dir = None
37 bin_tars = None
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020038 log_targets = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020039
40 @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):
54 self.path = trial_dir
Your Name3c6673a2017-04-08 18:52:39 +020055 self.set_name(os.path.basename(self.path))
Neels Hofmeyr3531a192017-03-28 14:30:28 +020056 self.set_log_category(log.C_TST)
57 self.dir = util.Dir(self.path)
58 self.inst_dir = util.Dir(self.dir.child('inst'))
59 self.bin_tars = []
60
61 def __repr__(self):
62 return self.name()
63
64 def __enter__(self):
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020065 # add a log target to log to the run dir
66 run_dir = self.get_run_dir()
67 self.log_targets = [
68 log.FileLogTarget(run_dir.new_child(FILE_LOG))
69 .set_all_levels(log.L_DBG)
70 .style_change(trace=True),
71 ]
Neels Hofmeyr3531a192017-03-28 14:30:28 +020072 self.log('Trial start')
Neels Hofmeyr506edbc2017-05-06 21:56:27 +020073 self.take()
Neels Hofmeyr3531a192017-03-28 14:30:28 +020074 super().__enter__()
75
76 def __exit__(self, *exc_info):
77 super().__exit__(*exc_info)
78 self.log('Trial end')
79
Neels Hofmeyrfd7b9d02017-05-05 19:51:40 +020080 for lt in self.log_targets:
81 lt.remove()
82 self.log_targets = None
83
Neels Hofmeyr3531a192017-03-28 14:30:28 +020084 def take(self):
85 self.dir.touch(FILE_MARK_TAKEN)
86 return self
87
88 def get_run_dir(self):
89 if self._run_dir is not None:
90 return self._run_dir
91 self._run_dir = util.Dir(self.dir.new_child('run.%s' % time.strftime(TIMESTAMP_FMT)))
92 self._run_dir.mkdir()
93
94 last_run = self.dir.child(FILE_LAST_RUN)
95 if os.path.islink(last_run):
96 os.remove(last_run)
97 if not os.path.exists(last_run):
98 os.symlink(self.dir.rel_path(self._run_dir.path), last_run)
99 return self._run_dir
100
101 def verify(self):
102 "verify checksums"
103
104 if not self.dir.exists():
105 raise RuntimeError('Trial dir does not exist: %r' % self.dir)
106 if not self.dir.isdir():
107 raise RuntimeError('Trial dir is not a dir: %r' % self.dir)
108
109 checksums = self.dir.child(FILE_CHECKSUMS)
110 if not self.dir.isfile(FILE_CHECKSUMS):
111 raise RuntimeError('No checksums file in trial dir: %r', checksums)
112
113 with open(checksums, 'r') as f:
114 line_nr = 0
115 for line in [l.strip() for l in f.readlines()]:
116 line_nr += 1
117 if not line:
118 continue
119 md5, filename = line.split(' ')
120 file_path = self.dir.child(filename)
121
122 if not self.dir.isfile(filename):
123 raise RuntimeError('File listed in checksums file but missing in trials dir:'
124 ' %r vs. %r line %d' % (file_path, checksums, line_nr))
125
126 if md5 != util.md5_of_file(file_path):
127 raise RuntimeError('Checksum mismatch for %r vs. %r line %d'
128 % (file_path, checksums, line_nr))
129
130 if filename.endswith('.tgz'):
131 self.bin_tars.append(filename)
132
133 def has_bin_tar(self, bin_name):
134 bin_tar_start = '%s.' % bin_name
135 matches = [t for t in self.bin_tars if t.startswith(bin_tar_start)]
136 self.dbg(bin_name=bin_name, matches=matches)
137 if not matches:
138 return None
139 if len(matches) > 1:
140 raise RuntimeError('More than one match for bin name %r: %r' % (bin_name, matches))
141 bin_tar = matches[0]
142 bin_tar_path = self.dir.child(bin_tar)
143 if not os.path.isfile(bin_tar_path):
144 raise RuntimeError('Not a file or missing: %r' % bin_tar_path)
145 return bin_tar_path
146
147 def get_inst(self, bin_name):
148 bin_tar = self.has_bin_tar(bin_name)
149 if not bin_tar:
Your Name3c6673a2017-04-08 18:52:39 +0200150 raise RuntimeError('No such binary available: %r' % bin_name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200151 inst_dir = self.inst_dir.child(bin_name)
152
153 if os.path.isdir(inst_dir):
154 # already unpacked
155 return inst_dir
156
157 t = None
158 try:
159 os.makedirs(inst_dir)
160 t = tarfile.open(bin_tar)
161 t.extractall(inst_dir)
162 return inst_dir
163
164 except:
165 shutil.rmtree(inst_dir)
166 raise
167 finally:
168 if t:
169 try:
170 t.close()
171 except:
172 pass
173
174# vim: expandtab tabstop=4 shiftwidth=4