Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 1 | # 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 | |
| 20 | import os |
| 21 | import time |
| 22 | import shutil |
| 23 | import tarfile |
| 24 | |
| 25 | from . import log, util |
| 26 | |
| 27 | FILE_MARK_TAKEN = 'taken' |
| 28 | FILE_CHECKSUMS = 'checksums.md5' |
| 29 | TIMESTAMP_FMT = '%Y-%m-%d_%H-%M-%S' |
| 30 | FILE_LAST_RUN = 'last_run' |
Neels Hofmeyr | fd7b9d0 | 2017-05-05 19:51:40 +0200 | [diff] [blame] | 31 | FILE_LOG = 'log' |
Neels Hofmeyr | 4f33dcc | 2017-05-07 00:01:09 +0200 | [diff] [blame] | 32 | FILE_LOG_BRIEF = 'log_brief' |
Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 33 | |
| 34 | class Trial(log.Origin): |
| 35 | path = None |
| 36 | dir = None |
| 37 | _run_dir = None |
| 38 | bin_tars = None |
Neels Hofmeyr | fd7b9d0 | 2017-05-05 19:51:40 +0200 | [diff] [blame] | 39 | log_targets = None |
Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 40 | |
| 41 | @staticmethod |
| 42 | def next(trials_dir): |
| 43 | |
| 44 | with trials_dir.lock('Trial.next'): |
| 45 | trials = [e for e in trials_dir.children() |
| 46 | if trials_dir.isdir(e) and not trials_dir.exists(e, FILE_MARK_TAKEN)] |
| 47 | if not trials: |
| 48 | return None |
| 49 | # sort by time to get the one that waited longest |
| 50 | trials.sort(key=lambda e: os.path.getmtime(trials_dir.child(e))) |
| 51 | next_trial = trials[0] |
| 52 | return Trial(trials_dir.child(next_trial)).take() |
| 53 | |
| 54 | def __init__(self, trial_dir): |
| 55 | self.path = trial_dir |
Your Name | 3c6673a | 2017-04-08 18:52:39 +0200 | [diff] [blame] | 56 | self.set_name(os.path.basename(self.path)) |
Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 57 | self.set_log_category(log.C_TST) |
| 58 | self.dir = util.Dir(self.path) |
| 59 | self.inst_dir = util.Dir(self.dir.child('inst')) |
| 60 | self.bin_tars = [] |
| 61 | |
| 62 | def __repr__(self): |
| 63 | return self.name() |
| 64 | |
| 65 | def __enter__(self): |
Neels Hofmeyr | fd7b9d0 | 2017-05-05 19:51:40 +0200 | [diff] [blame] | 66 | # add a log target to log to the run dir |
| 67 | run_dir = self.get_run_dir() |
| 68 | self.log_targets = [ |
| 69 | log.FileLogTarget(run_dir.new_child(FILE_LOG)) |
| 70 | .set_all_levels(log.L_DBG) |
| 71 | .style_change(trace=True), |
Neels Hofmeyr | 4f33dcc | 2017-05-07 00:01:09 +0200 | [diff] [blame] | 72 | log.FileLogTarget(run_dir.new_child(FILE_LOG_BRIEF)) |
| 73 | .style_change(src=False, all_origins=False) |
Neels Hofmeyr | fd7b9d0 | 2017-05-05 19:51:40 +0200 | [diff] [blame] | 74 | ] |
Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 75 | self.log('Trial start') |
Neels Hofmeyr | 506edbc | 2017-05-06 21:56:27 +0200 | [diff] [blame] | 76 | self.take() |
Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 77 | super().__enter__() |
| 78 | |
| 79 | def __exit__(self, *exc_info): |
| 80 | super().__exit__(*exc_info) |
| 81 | self.log('Trial end') |
| 82 | |
Neels Hofmeyr | fd7b9d0 | 2017-05-05 19:51:40 +0200 | [diff] [blame] | 83 | for lt in self.log_targets: |
| 84 | lt.remove() |
| 85 | self.log_targets = None |
| 86 | |
Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 87 | def take(self): |
| 88 | self.dir.touch(FILE_MARK_TAKEN) |
| 89 | return self |
| 90 | |
| 91 | def get_run_dir(self): |
| 92 | if self._run_dir is not None: |
| 93 | return self._run_dir |
| 94 | self._run_dir = util.Dir(self.dir.new_child('run.%s' % time.strftime(TIMESTAMP_FMT))) |
| 95 | self._run_dir.mkdir() |
| 96 | |
| 97 | last_run = self.dir.child(FILE_LAST_RUN) |
| 98 | if os.path.islink(last_run): |
| 99 | os.remove(last_run) |
| 100 | if not os.path.exists(last_run): |
| 101 | os.symlink(self.dir.rel_path(self._run_dir.path), last_run) |
| 102 | return self._run_dir |
| 103 | |
| 104 | def verify(self): |
| 105 | "verify checksums" |
| 106 | |
| 107 | if not self.dir.exists(): |
| 108 | raise RuntimeError('Trial dir does not exist: %r' % self.dir) |
| 109 | if not self.dir.isdir(): |
| 110 | raise RuntimeError('Trial dir is not a dir: %r' % self.dir) |
| 111 | |
| 112 | checksums = self.dir.child(FILE_CHECKSUMS) |
| 113 | if not self.dir.isfile(FILE_CHECKSUMS): |
| 114 | raise RuntimeError('No checksums file in trial dir: %r', checksums) |
| 115 | |
| 116 | with open(checksums, 'r') as f: |
| 117 | line_nr = 0 |
| 118 | for line in [l.strip() for l in f.readlines()]: |
| 119 | line_nr += 1 |
| 120 | if not line: |
| 121 | continue |
| 122 | md5, filename = line.split(' ') |
| 123 | file_path = self.dir.child(filename) |
| 124 | |
| 125 | if not self.dir.isfile(filename): |
| 126 | raise RuntimeError('File listed in checksums file but missing in trials dir:' |
| 127 | ' %r vs. %r line %d' % (file_path, checksums, line_nr)) |
| 128 | |
| 129 | if md5 != util.md5_of_file(file_path): |
| 130 | raise RuntimeError('Checksum mismatch for %r vs. %r line %d' |
| 131 | % (file_path, checksums, line_nr)) |
| 132 | |
| 133 | if filename.endswith('.tgz'): |
| 134 | self.bin_tars.append(filename) |
| 135 | |
| 136 | def has_bin_tar(self, bin_name): |
| 137 | bin_tar_start = '%s.' % bin_name |
| 138 | matches = [t for t in self.bin_tars if t.startswith(bin_tar_start)] |
| 139 | self.dbg(bin_name=bin_name, matches=matches) |
| 140 | if not matches: |
| 141 | return None |
| 142 | if len(matches) > 1: |
| 143 | raise RuntimeError('More than one match for bin name %r: %r' % (bin_name, matches)) |
| 144 | bin_tar = matches[0] |
| 145 | bin_tar_path = self.dir.child(bin_tar) |
| 146 | if not os.path.isfile(bin_tar_path): |
| 147 | raise RuntimeError('Not a file or missing: %r' % bin_tar_path) |
| 148 | return bin_tar_path |
| 149 | |
| 150 | def get_inst(self, bin_name): |
| 151 | bin_tar = self.has_bin_tar(bin_name) |
| 152 | if not bin_tar: |
Your Name | 3c6673a | 2017-04-08 18:52:39 +0200 | [diff] [blame] | 153 | raise RuntimeError('No such binary available: %r' % bin_name) |
Neels Hofmeyr | 3531a19 | 2017-03-28 14:30:28 +0200 | [diff] [blame] | 154 | inst_dir = self.inst_dir.child(bin_name) |
| 155 | |
| 156 | if os.path.isdir(inst_dir): |
| 157 | # already unpacked |
| 158 | return inst_dir |
| 159 | |
| 160 | t = None |
| 161 | try: |
| 162 | os.makedirs(inst_dir) |
| 163 | t = tarfile.open(bin_tar) |
| 164 | t.extractall(inst_dir) |
| 165 | return inst_dir |
| 166 | |
| 167 | except: |
| 168 | shutil.rmtree(inst_dir) |
| 169 | raise |
| 170 | finally: |
| 171 | if t: |
| 172 | try: |
| 173 | t.close() |
| 174 | except: |
| 175 | pass |
| 176 | |
| 177 | # vim: expandtab tabstop=4 shiftwidth=4 |