X-Git-Url: https://jxself.org/git/?p=open-adventure.git;a=blobdiff_plain;f=tests%2Fcoverage_dungeon.py;h=5e5f8606ab08daf7b1b07661105916827170c005;hp=b65094a5089f8d24a071d0290f05bc061e0c6a76;hb=fb86d64b20f05e5a2920ba76fb908486c8a67ba0;hpb=4446c61d5fcde70839a8cf3bc88b8463af0e5242 diff --git a/tests/coverage_dungeon.py b/tests/coverage_dungeon.py old mode 100644 new mode 100755 index b65094a..5e5f860 --- a/tests/coverage_dungeon.py +++ b/tests/coverage_dungeon.py @@ -1,60 +1,112 @@ -#!/usr/bin/python3 +#!/usr/bin/env python + +# This is the open-adventure dungeon text coverage report generator. It +# consumes a YAML description of the dungeon and determines whether the +# various strings contained are present within the test check files. +# +# The default HTML output is appropriate for use with Gitlab CI. +# You can override it with a command-line argument. import os +import sys import yaml +import re -import pprint +TEST_DIR = "." +YAML_PATH = "../adventure.yaml" +HTML_TEMPLATE_PATH = "coverage_dungeon.html.tpl" +DEFAULT_HTML_OUTPUT_PATH = "../coverage/adventure.yaml.html" -test_dir = "." -yaml_name = "../adventure.yaml" -html_template_path = "coverage_dungeon.html.tpl" -html_output_path = "../coverage/adventure.yaml.html" +STDOUT_REPORT_CATEGORY = " {name:.<19}: {percent}% covered ({covered} of {total}))\n" -location_row = """ +HTML_SUMMARY_ROW = """ - {} -   -   + {name}: + {total} + {covered} + {percent}% """ -arb_msg_row = """ +HTML_CATEGORY_TABLE = """ + + {rows} + +   + +""" + +HTML_CATEGORY_TABLE_HEADER_3_FIELDS = """ + + {} + {} + {} + +""" + +HTML_CATEGORY_TABLE_HEADER_2_FIELDS = """ + + {} + {} + +""" + +HTML_CATEGORY_ROW_3_FIELDS = """ {}   +   """ -object_row = """ +HTML_CATEGORY_ROW_2_FIELDS = """ - {} + {}   """ +def search(needle, haystack): + # Search for needle in haystack, first escaping needle for regex, then + # replacing %s, %d, etc. with regex wildcards, so the variable messages + # within the dungeon definition will actually match + needle = re.escape(needle) \ + .replace("\\n", "\n") \ + .replace("\\t", "\t") \ + .replace("\%S", ".*") \ + .replace("\%s", ".*") \ + .replace("\%d", ".*") \ + .replace("\%V", ".*") + + return re.search(needle, haystack) + def loc_coverage(locations, text): + # locations have a long and a short description, that each have to + # be checked seperately for locname, loc in locations: if loc["description"]["long"] == None or loc["description"]["long"] == '': loc["description"]["long"] = True if loc["description"]["long"] != True: - if text.find(loc["description"]["long"]) != -1: + if search(loc["description"]["long"], text): loc["description"]["long"] = True if loc["description"]["short"] == None or loc["description"]["short"] == '': loc["description"]["short"] = True if loc["description"]["short"] != True: - if text.find(loc["description"]["short"]) != -1: + if search(loc["description"]["short"], text): loc["description"]["short"] = True def arb_coverage(arb_msgs, text): + # arbitrary messages are a map to tuples for i, msg in enumerate(arb_msgs): (msg_name, msg_text) = msg if msg_text == None or msg_text == '': arb_msgs[i] = (msg_name, True) elif msg_text != True: - if text.find(msg_text) != -1: + if search(msg_text, text): arb_msgs[i] = (msg_name, True) def obj_coverage(objects, text): + # objects have multiple descriptions based on state for i, objouter in enumerate(objects): (obj_name, obj) = objouter if obj["descriptions"]: @@ -63,87 +115,243 @@ def obj_coverage(objects, text): obj["descriptions"][j] = True objects[i] = (obj_name, obj) elif desc != True: - if text.find(desc) != -1: + if search(desc, text): obj["descriptions"][j] = True objects[i] = (obj_name, obj) +def hint_coverage(hints, text): + # hints have a "question" where the hint is offered, followed + # by the actual hint if the player requests it + for i, hint in enumerate(hints): + if hint["hint"]["question"] != True: + if search(hint["hint"]["question"], text): + hint["hint"]["question"] = True + if hint["hint"]["hint"] != True: + if search(hint["hint"]["hint"], text): + hint["hint"]["hint"] = True + +def obit_coverage(obituaries, text): + # obituaries have a "query" where it asks the player for a resurrection, + # followed by a snarky comment if the player says yes + for i, obit in enumerate(obituaries): + if obit["query"] != True: + if search(obit["query"], text): + obit["query"] = True + if obit["yes_response"] != True: + if search(obit["yes_response"], text): + obit["yes_response"] = True + +def threshold_coverage(classes, text): + # works for class thresholds and turn threshold, which have a "message" + # property + for i, msg in enumerate(classes): + if msg["message"] == None: + msg["message"] = True + elif msg["message"] != True: + if search(msg["message"], text): + msg["message"] = True + +def specials_actions_coverage(items, text): + # works for actions or specials + for name, item in items: + if item["message"] == None or item["message"] == "NO_MESSAGE": + item["message"] = True + if item["message"] != True: + if search(item["message"], text): + item["message"] = True + if __name__ == "__main__": - with open(yaml_name, "r") as f: + with open(YAML_PATH, "r") as f: db = yaml.load(f) - with open(html_template_path, "r") as f: - html_template = f.read() + # Create report for each catagory, including HTML table, total items, + # and number of items covered + report = {} + for name in db.keys(): + # initialize each catagory + report[name] = { + "name" : name, # convenience for string formatting + "html" : "", + "total" : 0, + "covered" : 0 + } + motions = db["motions"] locations = db["locations"] arb_msgs = db["arbitrary_messages"] objects = db["objects"] + hints = db["hints"] + classes = db["classes"] + turn_thresholds = db["turn_thresholds"] + obituaries = db["obituaries"] + actions = db["actions"] + specials = db["specials"] text = "" - for filename in os.listdir(test_dir): + for filename in os.listdir(TEST_DIR): if filename.endswith(".chk"): with open(filename, "r") as chk: text = chk.read() loc_coverage(locations, text) arb_coverage(arb_msgs, text) obj_coverage(objects, text) + hint_coverage(hints, text) + threshold_coverage(classes, text) + threshold_coverage(turn_thresholds, text) + obit_coverage(obituaries, text) + specials_actions_coverage(actions, text) + specials_actions_coverage(specials, text) - print("\nadventure.yaml coverage rate:") - - location_html = "" - location_total = len(locations) * 2 - location_covered = 0 + report["locations"]["total"] = len(locations) * 2 + report["locations"]["html"] = HTML_CATEGORY_TABLE_HEADER_3_FIELDS.format("Location ID", "long", "short") + locations.sort() for locouter in locations: locname = locouter[0] loc = locouter[1] - long_success = "covered" - short_success = "covered" if loc["description"]["long"] != True: long_success = "uncovered" else: - location_covered += 1 + long_success = "covered" + report["locations"]["covered"] += 1 if loc["description"]["short"] != True: short_success = "uncovered" else: - location_covered += 1 + short_success = "covered" + report["locations"]["covered"] += 1 - location_html += location_row.format(locname, long_success, short_success) - location_percent = round((location_covered / location_total) * 100, 1) - print(" locations..........: {}% covered ({} of {})".format(location_percent, location_covered, location_total)) + report["locations"]["html"] += HTML_CATEGORY_ROW_3_FIELDS.format(locname, long_success, short_success) - arb_msg_html = "" - arb_total = len(arb_msgs) - arb_covered = 0 + arb_msgs.sort() + report["arbitrary_messages"]["total"] = len(arb_msgs) + report["arbitrary_messages"]["html"] = HTML_CATEGORY_TABLE_HEADER_2_FIELDS.format("Arbitrary Message ID", "covered") for name, msg in arb_msgs: - success = "covered" if msg != True: success = "uncovered" else: - arb_covered += 1 - arb_msg_html += arb_msg_row.format(name, success) - arb_percent = round((arb_covered / arb_total) * 100, 1) - print(" arbitrary_messages.: {}% covered ({} of {})".format(arb_percent, arb_covered, arb_total)) - - object_html = "" - objects_total = 0 - objects_covered = 0 + success = "covered" + report["arbitrary_messages"]["covered"] += 1 + report["arbitrary_messages"]["html"] += HTML_CATEGORY_ROW_2_FIELDS.format(name, success) + + objects.sort() + report["objects"]["html"] = HTML_CATEGORY_TABLE_HEADER_2_FIELDS.format("Object ID", "covered") for (obj_name, obj) in objects: if obj["descriptions"]: for j, desc in enumerate(obj["descriptions"]): - objects_total += 1 - success = "covered" + report["objects"]["total"] += 1 if desc != True: success = "uncovered" else: - objects_covered += 1 - object_html += object_row.format("%s[%d]" % (obj_name, j), success) - objects_percent = round((objects_covered / objects_total) * 100, 1) - print(" objects............: {}% covered ({} of {})".format(objects_percent, objects_covered, objects_total)) - - with open(html_output_path, "w") as f: - f.write(html_template.format( - location_total, location_covered, location_percent, - arb_total, arb_covered, arb_percent, - objects_total, objects_covered, objects_percent, - location_html, arb_msg_html, object_html - )) + success = "covered" + report["objects"]["covered"] += 1 + report["objects"]["html"] += HTML_CATEGORY_ROW_2_FIELDS.format("%s[%d]" % (obj_name, j), success) + + hints.sort() + report["hints"]["total"] = len(hints) * 2 + report["hints"]["html"] = HTML_CATEGORY_TABLE_HEADER_3_FIELDS.format("Hint ID", "question", "hint") + for i, hint in enumerate(hints): + hintname = hint["hint"]["name"] + if hint["hint"]["question"] != True: + question_success = "uncovered" + else: + question_success = "covered" + report["hints"]["covered"] += 1 + if hint["hint"]["hint"] != True: + hint_success = "uncovered" + else: + hint_success = "covered" + report["hints"]["covered"] += 1 + report["hints"]["html"] += HTML_CATEGORY_ROW_3_FIELDS.format(name, question_success, hint_success) + + report["classes"]["total"] = len(classes) + report["classes"]["html"] = HTML_CATEGORY_TABLE_HEADER_2_FIELDS.format("Adventurer Class #", "covered") + for name, msg in enumerate(classes): + if msg["message"] != True: + success = "uncovered" + else: + success = "covered" + report["classes"]["covered"] += 1 + report["classes"]["html"] += HTML_CATEGORY_ROW_2_FIELDS.format(msg["threshold"], success) + + report["turn_thresholds"]["total"] = len(turn_thresholds) + report["turn_thresholds"]["html"] = HTML_CATEGORY_TABLE_HEADER_2_FIELDS.format("Turn Threshold", "covered") + for name, msg in enumerate(turn_thresholds): + if msg["message"] != True: + success = "uncovered" + else: + success = "covered" + report["turn_thresholds"]["covered"] += 1 + report["turn_thresholds"]["html"] += HTML_CATEGORY_ROW_2_FIELDS.format(msg["threshold"], success) + + report["obituaries"]["total"] = len(obituaries) * 2 + report["obituaries"]["html"] = HTML_CATEGORY_TABLE_HEADER_3_FIELDS.format("Obituary #", "query", "yes_response") + for i, obit in enumerate(obituaries): + if obit["query"] != True: + query_success = "uncovered" + else: + query_success = "covered" + report["obituaries"]["covered"] += 1 + if obit["yes_response"] != True: + obit_success = "uncovered" + else: + obit_success = "covered" + report["obituaries"]["covered"] += 1 + report["obituaries"]["html"] += HTML_CATEGORY_ROW_3_FIELDS.format(i, query_success, obit_success) + + actions.sort() + report["actions"]["total"] = len(actions) + report["actions"]["html"] = HTML_CATEGORY_TABLE_HEADER_2_FIELDS.format("Action ID", "covered") + for name, action in actions: + if action["message"] != True: + success = "uncovered" + else: + success = "covered" + report["actions"]["covered"] += 1 + report["actions"]["html"] += HTML_CATEGORY_ROW_2_FIELDS.format(name, success) + + report["specials"]["total"] = len(specials) + report["specials"]["html"] = HTML_CATEGORY_TABLE_HEADER_2_FIELDS.format("Special ID", "covered") + for name, special in specials: + if special["message"] != True: + success = "uncovered" + else: + success = "covered" + report["specials"]["covered"] += 1 + report["specials"]["html"] += HTML_CATEGORY_ROW_2_FIELDS.format(name, success) + + # calculate percentages for each catagory and HTML for category tables + categories_html = "" + summary_html = "" + summary_stdout = "adventure.yaml coverage rate:\n" + for name, category in sorted(report.items()): + if(category["total"] > 0): + report[name]["percent"] = round((category["covered"] / float(category["total"])) * 100, 1) + summary_stdout += STDOUT_REPORT_CATEGORY.format(**report[name]) + categories_html += HTML_CATEGORY_TABLE.format(id=name, rows=category["html"]) + summary_html += HTML_SUMMARY_ROW.format(**report[name]) + else: + report[name]["percent"] = 100; + + # output some quick report stats + print(summary_stdout) + + if len(sys.argv) > 1: + html_output_path = sys.argv[1] + else: + html_output_path = DEFAULT_HTML_OUTPUT_PATH + + # render HTML report + try: + with open(HTML_TEMPLATE_PATH, "r") as f: + # read in HTML template + html_template = f.read() + except IOError as e: + print 'ERROR: reading HTML report template failed (%s)' % e.strerror + exit(-1) + + # parse template with report and write it out + try: + with open(html_output_path, "w") as f: + f.write(html_template.format(categories=categories_html, summary=summary_html)) + except IOError as e: + print 'ERROR: writing HTML report failed (%s)' % e.strerror