diff --git a/basic/program.py b/basic/program.py index 0e08835..acbc97b 100644 --- a/basic/program.py +++ b/basic/program.py @@ -310,5 +310,11 @@ class Configuration: doc = yaml.full_load(file) for i, v in doc.items(): self.confs[i] = v - print ("set conf") - print ("set configuration") + def setConfig(self, path, val): + a = path.split(".") + if len(a) == 1: + self.confs[a[0]] = val + elif len(a) == 2: + self.confs[a[0]][a[1]] = val + elif len(a) == 3: + self.confs[a[0]][a[1]][a[2]] = val diff --git a/test/test_compare.py b/test/test_compare.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_css.py b/test/test_css.py new file mode 100644 index 0000000..8341ca8 --- /dev/null +++ b/test/test_css.py @@ -0,0 +1,53 @@ +import unittest +import utils.css_tool +import basic.program +import json + +class MyTestCase(unittest.TestCase): + def test_css(self): + job = basic.program.Job("unit") + args = {"application": "TEST", "application": "ENV01", "modus": "unit", "loglevel": "debug", + "tool": "job_tool", "tdtyp": "csv", "tdsrc": "implement", "tdname": "firstunit", + "modus": "unit"} + job.par.setParameterArgs(args) + print("eeeeeeeee") + print(json.dumps(job.conf.confs)) + # ------- inline --------------- + job.conf.setConfig("tools.csstyp", "inline") + job.conf.confs.get("tools")["csstyp"] == "inline" + text = utils.css_tool.getInlineStyle("diffFiles", "diffA") + self.assertEqual(len(text), 37) + self.assertEqual(("style" in text), True) + text = utils.css_tool.getInternalStyle("diffFiles") + self.assertEqual(len(text), 0) + text = utils.css_tool.getExternalStyle("diffFiles") + self.assertEqual(len(text), 0) + # ------- internal --------------- + job.conf.setConfig("tools.csstyp", "internal") + text = utils.css_tool.getInlineStyle("diffFiles", "diffA") + self.assertEqual(len(text), 13) + self.assertEqual(("class" in text), True) + text = utils.css_tool.getInternalStyle("diffFiles") + print(text) + self.assertEqual(len(text), 173) + self.assertEqual(("" + elif job.conf.confs.get("tools").get("csstyp") == "external": + out = " " + else: + out = "" + return out + +def getExternalStyle(filetype): + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool")) - 4 + job.debug(verify, "getDiffHeader ") + out = "" + if job.conf.confs.get("tools").get("csstyp") == "external": + for c in CSS_CLASS[filetype]: + out += c+" {\n "+CSS_CLASS[filetype][c].replace(":", ": ").replace(";", ";\n ")+"}\n" + out.replace("\n \n}", "\n}") + return out diff --git a/utils/match_tool.py b/utils/match_tool.py index 39a4e2a..b1319f6 100644 --- a/utils/match_tool.py +++ b/utils/match_tool.py @@ -1,16 +1,145 @@ # +import json +import utils.css_tool +import basic.program # ------------------------------------------------------------ """ """ +MATCH = { + "preconditions": { + "A": "preReq", + "B": "preAct", + "shortA": "SV", + "shortB": "IV", + "longA": "Soll-Vorher", + "longB": "Ist-Vorher", + "mode": "info", + "title": "Pruefung Vorbedingung (Soll-Vorher - Ist-Vorher)" + }, + "postconditions": { + "A": "postReq", + "B": "postAct", + "shortA": "SN", + "shortB": "IN", + "longA": "Soll-Nachher", + "longB": "Ist-Nachher", + "mode": "hard", + "title": "Fachliche Auswertung (Soll-Nachher - Ist-Nachher)" + }, + "prepost": { + "A": "preAct", + "B": "postAct", + "shortA": "IV", + "shortB": "IN", + "longA": "Ist-Vorher", + "longB": "Ist-Nachher", + "mode": "action", + "title": "Ablauf-Differenz (Ist-Vorher - Ist-Nachher)" + }, + "prestep": { + "A": "preStep", + "B": "postAct", + "shortA": "VS", + "shortB": "IN", + "longA": "Vor-Schritt", + "longB": "Ist-Nachher", + "mode": "action", + "title": "Schritt-Differenz (Vorschritt-Nachher - Ist-Nachher)" + } +} +MATCH_PREPOST = "prepost" +""" matches the action between pre- and postcondition of the actual testexecution """ +MATCH_PRECOND = "preconditions" +""" matches the preconditions betwenn the required result the the actual testexecution + - just for info if the both executions have the same precondition """ +MATCH_POSTCOND = "postconditions" +""" matches the postconditions betwenn the required result the the actual testexecution + - it is the main comparison """ + + class Matching(): def __init__(self, comp): self.comp = comp self.elements = {} - self.resultFiles = comp.resultFiles - self.targetFiles = comp.targetFiles + self.matchfiles = comp.files self.assignedFiles = {} - pass + self.linksA = {} + self.linksB = {} + self.nomatch = {} + self.matchkeys = {} + self.htmltext = "" + self.sideA = [] + self.sideB = [] + self.mode = "" + self.matchtype = "" + + def setData(self, tdata, match): + """ + selects testdata for actual matching + :param tdata: + :param match: kind of actual match + :return: + """ + self.sideA = tdata[MATCH[match]["A"]] + self.sideB = tdata[MATCH[match]["B"]] + self.matchtype = match + self.mode = MATCH[match]["mode"] + self.setDiffHeader() + + def resetHits(self): + self.linksA = {} + self.linksB = {} + self.nomatch = {} + self.matchkeys = {} + + def isHitA(self, key): + return ((key in self.linksA) and (self.linksA[key] != "null")) + def isHitB(self, key): + return ((key in self.linksB) and (self.linksB[key] != "null")) + def setHit(self, keyA, keyB): + if (not self.isHitA(keyA)) and (not self.isHitB(keyB)): + if (keyA != "null"): self.linksA[keyA] = keyB + if (keyB != "null"): self.linksB[keyB] = keyA + return "OK" + raise Exception("one of the links are set") + def setNohit(self, similarity, keyA, keyB): + """ The similarity must be unique. Only a mismatch will be set. """ + if similarity in self.nomatch: + raise Exception("similarity "+similarity+" exists") + if (self.isHitA(keyA) or self.isHitB(keyB)): + return + self.nomatch[similarity] = [keyA, keyB] + def getTableDdl(self, path): + a = path.split(":") + ddl = self.comp.conf["ddl"] + for x in a: + if (len(x) < 2): continue + if (x == "_data"): break + if x in ddl: ddl = ddl[x] + return ddl + def setDiffHeader(matching): + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-4 + job.debug(verify, "getDiffHeader ") + htmltxt = "" + htmltxt += "" + htmltxt += ""+MATCH[matching.matchtype]["title"]+"" + htmltxt += utils.css_tool.getInternalStyle("diffFiles") + htmltxt += "" + htmltxt += "" + htmltxt += "

"+MATCH[matching.matchtype]["title"]+"

" + htmltxt += "

" + MATCH[matching.matchtype]["longA"] +": " + matching.matchfiles["A"] + "

" + htmltxt += "

" + MATCH[matching.matchtype]["longB"] +": " + matching.matchfiles["B"] + "


" + matching.htmltext = htmltxt + + def setDiffFooter(self): + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool")) - 4 + job.debug(verify, "getDiffFooter ") + htmltext = self.htmltext + htmltext += "" + self.htmltext = htmltext def matchFiles(matching): """ @@ -19,9 +148,8 @@ def matchFiles(matching): :param matching: :return: """ - pass -def matchBestfit(matching): +def matchBestfit(matching, path): """ in this strategy the difference-score of all elements of both sides will be calculated. the best fit will assigned together until nothing is @@ -30,35 +158,98 @@ def matchBestfit(matching): :param matching: :return: """ - hits = {} - output = [] - results = {} - targets = {} - for tg in matching.elements["tgelement"]: - targets[tg] = True - for rs in matching.elements["rselement"]: - results[rs] = True - for tg in matching.elements["tgelement"]: - acthit = matching.comp.getHitscore(matching.elementtyp, matching.elements["rselement"][rs], matching.elements["tgelement"][tg]) - while acthit in hits.keys(): - acthit = getNextHitscore(acthit) - hits[acthit] = {} - hits[acthit]["result"] = rs - hits[acthit]["target"] = tg - for h in sorted(hits.keys()): - if results[h]["result"] and targets[h]["target"]: - results[h]["result"] = False - targets[h]["target"] = False - output.append((hits[acthit]["result"], hits[acthit]["target"])) - for rs in results.keys(): - if results[rs]: - output.append((matching.elements["rselement"][rs], None)) - for tg in targets.keys(): - if results[rs]: - output.append((matching.elements["tgelement"][tg], None)) - return output + i=0 + for r in matching.sideA: + k = composeKey("a", i) + matching.setHit(k, "null") + i += 1 + i = 0 + for r in matching.sideB: + k = composeKey("b", i) + matching.setHit("null", k) + i += 1 + ia = 0 + ix = 1 + for rA in matching.sideA: + ib = 0 + for rB in matching.sideB: + if (matching.isHitB(composeKey("b", ib))): + ib += 1 + continue + similarity=getSimilarity(matching, path, rA, rB, ix) + if (similarity == "MATCH"): + matching.setHit(composeKey("a", ia), composeKey("b", ib)) + continue + else: + matching.setNohit(similarity, composeKey("a", ia), composeKey("b", ib)) + ib += 1 + ix += 1 + ia += 1 -# -------------------------------------------------------------------------- +def matchRestfit(matching): + """ """ + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-1 + job.debug(verify, "matchRestfit ") + for x in sorted(matching.nomatch, reverse=True): + job.debug(verify, "matchRestfit " +x) + pair = matching.nomatch[x] + if (matching.isHitA(pair[0])): + print("A "+pair[0]+" bereits zugeordnet mit "+matching.linksA[pair[0]]) + if (matching.isHitB(pair[1])): + print("B " + pair[1] + " bereits zugeordnet mit " + matching.linksB[pair[1]]) + continue + if (matching.isHitA(pair[0])): + continue + print("neues Matching "+pair[0]+" "+pair[1]) + matching.setHit(pair[0], pair[1]) + +def composeKey(side, i): + return side.lower()+str(i+1).zfill(4) +def extractKeyI(key): + return int(key[1:])-1 + +def getSimilarity(matching, path, rA, rB, i): + """ it calculates the similarity between both rows by: + concat each criteria with single-similarity 00..99 and i with 999..000 """ + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-1 + job.debug(verify, "getSimilarity "+path+" "+str(i)) + if len(matching.matchkeys) > 0: + keys = matching.matchkeys + elif ("ddl" in matching.comp.conf): + job.debug(verify, "ddl " + path + " " + str(i)) + a = path.split(":") + ddl = matching.comp.conf["ddl"] + for x in a: + if (len(x) < 2): continue + if (x == "_data"): break + if x in ddl: ddl = ddl[x] + job.debug(verify, "ddl " + json.dumps(ddl) + " " + str(i)) + keys = {} + for f in ddl: + job.debug(verify, "ddl " + f + " " + str(i)) + if ("key" in ddl[f]) and (len(ddl[f]["key"])>0): + b = ddl[f]["key"].split(":") + if (len(b)!=2): raise Exception("falsch formatierter Schluessel "+ddl[f]["key"]) + if (not b[1].isnumeric()): raise Exception("falsch formatierter Schluessel "+ddl[f]["key"]) + k = "k"+b[1].zfill(2) + job.debug(verify, "ddl " + f + " " + str(i)) + keys[k]={ "ktyp": b[0], "field": ddl[f]["feld"], "type": ddl[f]["type"], "rule": ddl[f]["acceptance"]} + matching.matchkeys=keys + job.debug(verify, "ddl " + json.dumps(keys) + " " + str(i)) + msim="" + topsim="" + for k in sorted(matching.matchkeys): + msim += getStringSimilarity(str(rA[matching.matchkeys[k]["field"]]), str(rB[matching.matchkeys[k]["field"]])) + topsim += "99" + if msim == topsim: + job.debug(verify, "Treffer ") + return "MATCH" + else: + job.debug(verify, "nomatch S"+msim+str(i).zfill(3)) + return "S"+msim+str(i).zfill(3) + pass def matchTree(matching): """ @@ -66,20 +257,156 @@ def matchTree(matching): :param matching: :return: """ - pass + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-4 + job.debug(verify, "matching "+matching.mode) + matchElement(matching, matching.sideA, matching.sideB, "") + matching.setDiffFooter() + return matching.htmltext + +def matchElement(matching, A, B, path): + """ travers through the datatree """ + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-4 + job.debug(verify, "matchElem "+path+" A "+str(type(A))+" B "+str(type(B))) + if ((A is not None) and (isinstance(A, list))) \ + or ((B is not None) and (isinstance(B, list))): + return matchArray(matching, A, B, path) + elif ((A is not None) and (isinstance(A, dict))) \ + or ((B is not None) and (isinstance(B, dict))): + return matchDict(matching, A, B, path) + else: + return matching + +def getStringSimilarity(strA, strB): + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-1 + job.debug(verify, "getStringSimilarity "+strA+" ?= "+strB) + if (strA == strB): return "99" + if (strA.strip() == strB.strip()): return "77" + if (strA.lower() == strB.lower()): return "66" + if (strA.strip().lower() == strB.strip().lower()): return "55" + return "00" + +def getEvaluation(matching, type, acceptance, sideA, sideB): + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-1 + job.debug(verify, "getEvaluation "+str(sideA)+" ?= "+str(sideB)) + match = getStringSimilarity(str(sideA), str(sideB)) + colorA = "black" + colorB = "black" + classA = "black" + classB = "black" + result = "test" + if match == "99": return ["MATCH", "black", "black", "black", "black"] + if acceptance == "ignore": result = "ignore" + if (matching.matchtype == MATCH_POSTCOND) and (result == "test"): + result = "hard" + colorA = "green" + colorB = "crimson" + classA = "diffA" + classB = "diffB" + else: + colorA = "darkblue" + colorB = "darkmagenta" + classA = "acceptA" + classB = "acceptB" + return [result, colorA, colorB, classA, classB] - def matchElement(matching, rs, tg): - if "array" in type(rs) and "array" in type(tg): - return matchArray(matching, rs, tg) - elif "dict" in type(rs) and "dict" in type(tg): - return matchDict(matching, rs, tg) +def matchDict(matching, A, B, path): + """ travers through the datatree """ + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-4 + job.debug(verify, "matchDict "+path) + for k in A: + print(k) + if k == "_match": + continue + if (B is not None) and (B[k]): + if (isinstance(A[k], dict)): A[k]["_match"] = "Y" + if (isinstance(B[k], dict)): B[k]["_match"] = "Y" + matchElement(matching, A[k], B[k], path+":"+k) else: - pass + if (isinstance(A[k], dict)): A[k]["_march"] = "N" + matchElement(matching, A[k], None, path+":"+k) + for k in B: + if k == "_match": + continue + if (A is not None) and (A[k]): + continue + else: + if (isinstance(A[k], dict)): B[k]["_match"] = "N" + matchElement(matching, None, B[k], path+":"+k) + return matching + +def matchArray(matching, A, B, path): + """ matches the datarows of the datatree """ + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-1 + job.debug(verify, "matchArray "+path) + matching.sideA = A + matching.sideB = B + matchBestfit(matching, path) + matchRestfit(matching) + htmltext = compareRows(matching, path) + matching.htmltext += htmltext - def matchDict(matching, rs, tg): - pass - def matchArray(matching, rs, tg): - pass # matchBest +def compareRows(matching, path): + """ traverse through matched rows """ + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-1 + ddl = matching.getTableDdl(path) + table = "" + a = path.split(":") + for x in a: + if (x == "_data"): break + table = x + header = [] + htmltext = "

Tabelle : "+table+"

" + htmltext += "" + htmltext += "" + for f in ddl["_header"]: + job.debug(verify, "ddl " + f + " ") + header.append({ "field": f, "type": ddl[f]["type"], "acceptance": ddl[f]["acceptance"]}) + htmltext += "" + htmltext += "" + for k in sorted(matching.linksA): + htmltext += compareRow(matching, header, matching.sideA[int(extractKeyI(k))], + matching.sideB[int(extractKeyI(matching.linksA[k]))]) + htmltext += "
"+f+"
" + return htmltext + +def compareRow(matching, header, rA, rB): + """ traverse through matched rows """ + job = basic.program.Job.getInstance() + verify = int(job.getDebugLevel("match_tool"))-4 + allident = True + textA = "" + textB = "" + for f in header: + print(f) + res = getEvaluation(matching, f["type"], f["acceptance"], rA[f["field"]], rB[f["field"]]) + match = res[0] + colorA = res[1] + colorB = res[2] + classA = res[3] + classB = res[4] + if (match == "MATCH"): + textA += ""+str(rA[f["field"]])+"" + textB += ""+str(rB[f["field"]])+"" + elif (match == "hard"): + allident = False + textA += "" + str(rA[f["field"]]) + "" + textB += "" + str(rB[f["field"]]) + "" + #textB += "" + str(rB[f["field"]]) + "" + else: + allident = False + textA += ""+str(rA[f["field"]])+" ("+match+")" + textB += ""+str(rB[f["field"]])+" ("+match+")" + #textB += ""+str(rA[f["field"]])+" ("+match+")" + if allident: + return ""+textA+"" + return ""+MATCH[matching.matchtype]["shortA"]+""+textA+""+MATCH[matching.matchtype]["shortB"]+""+textB+"" # -------------------------------------------------------------------------- def matchLines(matching):