|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | + |
| 4 | +from argparse import ArgumentParser |
| 5 | + |
| 6 | +import os.path as op |
| 7 | +import numpy as np |
| 8 | +import hashlib |
| 9 | +import json |
| 10 | +import os |
| 11 | +import re |
| 12 | + |
| 13 | + |
| 14 | +def get_seq_steplist(dirs): |
| 15 | + if not isinstance(dirs, list): |
| 16 | + dirs = [dirs] |
| 17 | + |
| 18 | + r = re.compile('.+_[0-9]+$') |
| 19 | + fl_lists = [] |
| 20 | + for di in dirs: |
| 21 | + fls = sorted(os.listdir(di)) |
| 22 | + fls_filtered = [f for f in fls if r.match(f)] |
| 23 | + fl_inds = [int(_.split('_')[-1]) for _ in fls_filtered] |
| 24 | + |
| 25 | + fls_sorted = [fls_filtered[_] for _ in np.argsort(fl_inds)] |
| 26 | + |
| 27 | + fl_lists += [fls_sorted] |
| 28 | + return fl_lists |
| 29 | + |
| 30 | + |
| 31 | +def prune_and_match(file_lists): |
| 32 | + ref = file_lists[0] |
| 33 | + new_flist = [ref] |
| 34 | + for flist in file_lists[1:]: |
| 35 | + new_flist += [ [item for item in flist if item in ref] ] |
| 36 | + return new_flist |
| 37 | + |
| 38 | + |
| 39 | +def find_outputs(dir_path): |
| 40 | + try: |
| 41 | + with open(op.join(dir_path, "_report/report.rst")) as fhandle: |
| 42 | + summary = fhandle.read() |
| 43 | + except FileNotFoundError: |
| 44 | + return {} |
| 45 | + |
| 46 | + summary = summary.split('\n\n\n') |
| 47 | + outp_loc = [1 + idx |
| 48 | + for idx, row in enumerate(summary) |
| 49 | + if 'Execution Outputs' in row][0] |
| 50 | + outputs = summary[outp_loc].split('\n') |
| 51 | + |
| 52 | + kys = [op.split()[1] for op in outputs] |
| 53 | + vls = [" ".join(op.split()[3:]) for op in outputs] |
| 54 | + |
| 55 | + fls = {} |
| 56 | + for k, v in zip(kys, vls): |
| 57 | + if v == '<undefined>': |
| 58 | + continue |
| 59 | + if v.startswith('['): |
| 60 | + v = json.loads(v.replace("'", '"')) |
| 61 | + else: |
| 62 | + v = [v] |
| 63 | + |
| 64 | + v = [op.basename(_) for _ in v] |
| 65 | + # Build the dictionary in reverse to easily look up output names |
| 66 | + for _ in v: |
| 67 | + fls[_] = k |
| 68 | + |
| 69 | + return fls |
| 70 | + |
| 71 | + |
| 72 | +def md5_compare(comparable_steps, basepath, path_mods): |
| 73 | + ref_list, ref_mod = comparable_steps[0], path_mods[0] |
| 74 | + hash_dict = {} |
| 75 | + for idx, fdirs in enumerate(comparable_steps): |
| 76 | + if idx == 0: |
| 77 | + dk = 'ref' |
| 78 | + setting = "Reference" |
| 79 | + else: |
| 80 | + dk = 'test' + str(idx) |
| 81 | + setting = "Test " + str(idx) |
| 82 | + |
| 83 | + hash_dict[dk] = {} |
| 84 | + print('Computing hashes for {0}...'.format(setting)) |
| 85 | + |
| 86 | + for fdir in fdirs: |
| 87 | + fpath = op.join(basepath, path_mods[idx], fdir) |
| 88 | + outputs = find_outputs(fpath) |
| 89 | + |
| 90 | + hash_dict[dk][fdir] = {} |
| 91 | + for key in outputs.keys(): |
| 92 | + tmp_fl = op.join(fpath, key) |
| 93 | + if op.exists(tmp_fl): |
| 94 | + hash_dict[dk][fdir][outputs[key]] = md5(tmp_fl) |
| 95 | + |
| 96 | + return hash_dict |
| 97 | + |
| 98 | + |
| 99 | +def md5(fl): |
| 100 | + # From: |
| 101 | + # https://stackoverflow.com/questions/22058048/hashing-a-file-in-python |
| 102 | + # BUF_SIZE is totally arbitrary, change for your app! |
| 103 | + BUF_SIZE = 65536 # lets read stuff in 64kb chunks! |
| 104 | + md5 = hashlib.md5() |
| 105 | + |
| 106 | + with open(fl, 'rb') as f: |
| 107 | + while True: |
| 108 | + data = f.read(BUF_SIZE) |
| 109 | + if not data: |
| 110 | + break |
| 111 | + md5.update(data) |
| 112 | + |
| 113 | + return md5.hexdigest() |
| 114 | + |
| 115 | + |
| 116 | +def flag_differences(file_hashes): |
| 117 | + tests = set(file_hashes.keys()) - set(['ref']) |
| 118 | + print("Comparing hashes...") |
| 119 | + for key in file_hashes['ref'].keys(): |
| 120 | + rdat = file_hashes['ref'][key] |
| 121 | + print(" Comparing {0}...".format(key)) |
| 122 | + for t in tests: |
| 123 | + tdat = file_hashes[t][key] |
| 124 | + print(" {0}:".format(t.capitalize()), end='\t') |
| 125 | + |
| 126 | + diff = [] |
| 127 | + for k in rdat.keys(): |
| 128 | + if rdat[k] == {}: |
| 129 | + diff += ["no data"] |
| 130 | + elif rdat[k] == tdat[k]: |
| 131 | + continue |
| 132 | + else: |
| 133 | + diff += [k] |
| 134 | + if len(diff) == 0: |
| 135 | + diff = "SUCCESS!" |
| 136 | + else: |
| 137 | + diff = "FAILED for " + ", ".join(diff) |
| 138 | + |
| 139 | + print(diff) |
| 140 | + |
| 141 | + |
| 142 | +def main(): |
| 143 | + parser = ArgumentParser() |
| 144 | + parser.add_argument("ref", help="Working directory for the reference.") |
| 145 | + parser.add_argument("test", help="Working directory for the test-setting.") |
| 146 | + |
| 147 | + res = parser.parse_args() |
| 148 | + |
| 149 | + step_lists = get_seq_steplist([res.ref, res.test]) |
| 150 | + comparable_steps = prune_and_match(step_lists) |
| 151 | + |
| 152 | + bp = op.commonpath([res.ref, res.test]) |
| 153 | + pdiffs = [op.relpath(p, bp) for p in [res.ref, res.test]] |
| 154 | + |
| 155 | + file_hashes = md5_compare(comparable_steps, bp, pdiffs) |
| 156 | + |
| 157 | + flag_differences(file_hashes) |
| 158 | + |
| 159 | + |
| 160 | +if __name__ == "__main__": |
| 161 | + main() |
0 commit comments