3 # This is the open-adventure dungeon text coverage report generator. It
4 # consumes a YAML description of the dungeon and determines whether the
5 # various strings contained are present within the test check files.
7 # The default HTML output is appropriate for use with Gitlab CI.
8 # You can override it with a command-line argument.
16 yaml_name = "../adventure.yaml"
17 html_template_path = "coverage_dungeon.html.tpl"
18 html_output_path = "../coverage/adventure.yaml.html"
22 <td class="coverFile">{}</td>
23 <td class="{}"> </td>
24 <td class="{}"> </td>
30 <td class="coverFile">{}</td>
31 <td class="{}"> </td>
35 def search(needle, haystack):
36 # Search for needle in haystack, first escaping needle for regex, then
37 # replacing %s, %d, etc. with regex wildcards, so the variable messages
38 # within the dungeon definition will actually match
39 needle = re.escape(needle) \
40 .replace("\\n", "\n") \
41 .replace("\\t", "\t") \
42 .replace("\%S", ".*") \
43 .replace("\%s", ".*") \
44 .replace("\%d", ".*") \
47 return re.search(needle, haystack)
49 def loc_coverage(locations, text):
50 # locations have a long and a short description, that each have to
51 # be checked seperately
52 for locname, loc in locations:
53 if loc["description"]["long"] == None or loc["description"]["long"] == '':
54 loc["description"]["long"] = True
55 if loc["description"]["long"] != True:
56 if search(loc["description"]["long"], text):
57 loc["description"]["long"] = True
58 if loc["description"]["short"] == None or loc["description"]["short"] == '':
59 loc["description"]["short"] = True
60 if loc["description"]["short"] != True:
61 if search(loc["description"]["short"], text):
62 loc["description"]["short"] = True
64 def arb_coverage(arb_msgs, text):
65 # arbitrary messages are a map to tuples
66 for i, msg in enumerate(arb_msgs):
67 (msg_name, msg_text) = msg
68 if msg_text == None or msg_text == '':
69 arb_msgs[i] = (msg_name, True)
70 elif msg_text != True:
71 if search(msg_text, text):
72 arb_msgs[i] = (msg_name, True)
74 def obj_coverage(objects, text):
75 # objects have multiple descriptions based on state
76 for i, objouter in enumerate(objects):
77 (obj_name, obj) = objouter
78 if obj["descriptions"]:
79 for j, desc in enumerate(obj["descriptions"]):
80 if desc == None or desc == '':
81 obj["descriptions"][j] = True
82 objects[i] = (obj_name, obj)
84 if search(desc, text):
85 obj["descriptions"][j] = True
86 objects[i] = (obj_name, obj)
88 def hint_coverage(hints, text):
89 # hints have a "question" where the hint is offered, followed
90 # by the actual hint if the player requests it
91 for name, hint in hints:
92 if hint["question"] != True:
93 if search(hint["question"], text):
94 hint["question"] = True
95 if hint["hint"] != True:
96 if search(hint["hint"], text):
99 def obit_coverage(obituaries, text):
100 # obituaries have a "query" where it asks the player for a resurrection,
101 # followed by a snarky comment if the player says yes
102 for i, obit in enumerate(obituaries):
103 if obit["query"] != True:
104 if search(obit["query"], text):
106 if obit["yes_response"] != True:
107 if search(obit["yes_response"], text):
108 obit["yes_response"] = True
110 def threshold_coverage(classes, text):
111 # works for class thresholds and turn threshold, which have a "message"
113 for i, msg in enumerate(classes):
114 if msg["message"] == None:
115 msg["message"] = True
116 elif msg["message"] != True:
117 if search(msg["message"], text):
118 msg["message"] = True
120 def specials_actions_coverage(items, text):
121 # works for actions or specials
122 for name, item in items:
123 if item["message"] == None or item["message"] == "NO_MESSAGE":
124 item["message"] = True
125 if item["message"] != True:
126 if search(item["message"], text):
127 item["message"] = True
129 if __name__ == "__main__":
130 with open(yaml_name, "r") as f:
133 with open(html_template_path, "r") as f:
134 html_template = f.read()
136 motions = db["motions"]
137 locations = db["locations"]
138 arb_msgs = db["arbitrary_messages"]
139 objects = db["objects"]
140 hintsraw = db["hints"]
141 classes = db["classes"]
142 turn_thresholds = db["turn_thresholds"]
143 obituaries = db["obituaries"]
144 actions = db["actions"]
145 specials = db["specials"]
148 for hint in hintsraw:
149 hints.append((hint["hint"]["name"], {"question" : hint["hint"]["question"],"hint" : hint["hint"]["hint"]}))
152 for filename in os.listdir(test_dir):
153 if filename.endswith(".chk"):
154 with open(filename, "r") as chk:
156 loc_coverage(locations, text)
157 arb_coverage(arb_msgs, text)
158 obj_coverage(objects, text)
159 hint_coverage(hints, text)
160 threshold_coverage(classes, text)
161 threshold_coverage(turn_thresholds, text)
162 obit_coverage(obituaries, text)
163 specials_actions_coverage(actions, text)
164 specials_actions_coverage(specials, text)
167 location_total = len(locations) * 2
170 for locouter in locations:
171 locname = locouter[0]
173 if loc["description"]["long"] != True:
174 long_success = "uncovered"
176 long_success = "covered"
177 location_covered += 1
179 if loc["description"]["short"] != True:
180 short_success = "uncovered"
182 short_success = "covered"
183 location_covered += 1
185 location_html += row_3_fields.format(locname, long_success, short_success)
186 location_percent = round((location_covered / float(location_total)) * 100, 1)
190 arb_total = len(arb_msgs)
192 for name, msg in arb_msgs:
194 success = "uncovered"
198 arb_msg_html += row_2_fields.format(name, success)
199 arb_percent = round((arb_covered / float(arb_total)) * 100, 1)
205 for (obj_name, obj) in objects:
206 if obj["descriptions"]:
207 for j, desc in enumerate(obj["descriptions"]):
210 success = "uncovered"
214 object_html += row_2_fields.format("%s[%d]" % (obj_name, j), success)
215 objects_percent = round((objects_covered / float(objects_total)) * 100, 1)
219 hints_total = len(hints) * 2
221 for name, hint in hints:
222 if hint["question"] != True:
223 question_success = "uncovered"
225 question_success = "covered"
227 if hint["hint"] != True:
228 hint_success = "uncovered"
230 hint_success = "covered"
232 hints_html += row_3_fields.format(name, question_success, hint_success)
233 hints_percent = round((hints_covered / float(hints_total)) * 100, 1)
236 class_total = len(classes)
238 for name, msg in enumerate(classes):
239 if msg["message"] != True:
240 success = "uncovered"
244 class_html += row_2_fields.format(msg["threshold"], success)
245 class_percent = round((class_covered / float(class_total)) * 100, 1)
248 turn_total = len(turn_thresholds)
250 for name, msg in enumerate(turn_thresholds):
251 if msg["message"] != True:
252 success = "uncovered"
256 turn_html += row_2_fields.format(msg["threshold"], success)
257 turn_percent = round((turn_covered / float(turn_total)) * 100, 1)
259 obituaries_html = "";
260 obituaries_total = len(obituaries) * 2
261 obituaries_covered = 0
262 for i, obit in enumerate(obituaries):
263 if obit["query"] != True:
264 query_success = "uncovered"
266 query_success = "covered"
267 obituaries_covered += 1
268 if obit["yes_response"] != True:
269 obit_success = "uncovered"
271 obit_success = "covered"
272 obituaries_covered += 1
273 obituaries_html += row_3_fields.format(i, query_success, obit_success)
274 obituaries_percent = round((obituaries_covered / float(obituaries_total)) * 100, 1)
278 actions_total = len(actions)
280 for name, action in actions:
281 if action["message"] != True:
282 success = "uncovered"
286 actions_html += row_2_fields.format(name, success)
287 actions_percent = round((actions_covered / float(actions_total)) * 100, 1)
290 special_total = len(specials)
292 for name, special in specials:
293 if special["message"] != True:
294 success = "uncovered"
298 special_html += row_2_fields.format(name, success)
299 special_percent = round((special_covered / float(special_total)) * 100, 1)
301 # output some quick report stats
302 print("\nadventure.yaml coverage rate:")
303 print(" locations..........: {}% covered ({} of {})".format(location_percent, location_covered, location_total))
304 print(" arbitrary_messages.: {}% covered ({} of {})".format(arb_percent, arb_covered, arb_total))
305 print(" objects............: {}% covered ({} of {})".format(objects_percent, objects_covered, objects_total))
306 print(" hints..............: {}% covered ({} of {})".format(hints_percent, hints_covered, hints_total))
307 print(" classes............: {}% covered ({} of {})".format(class_percent, class_covered, class_total))
308 print(" turn_thresholds....: {}% covered ({} of {})".format(turn_percent, turn_covered, turn_total))
309 print(" obituaries.........: {}% covered ({} of {})".format(obituaries_percent, obituaries_covered, obituaries_total))
310 print(" actions............: {}% covered ({} of {})".format(actions_percent, actions_covered, actions_total))
311 print(" specials...........: {}% covered ({} of {})".format(special_percent, special_covered, special_total))
313 if len(sys.argv) > 1:
314 html_output_path = sys.argv[1]
317 with open(html_output_path, "w") as f:
318 f.write(html_template.format(
319 location_total, location_covered, location_percent,
320 arb_total, arb_covered, arb_percent,
321 objects_total, objects_covered, objects_percent,
322 hints_total, hints_covered, hints_percent,
323 class_total, class_covered, class_percent,
324 turn_total, turn_covered, turn_percent,
325 obituaries_total, obituaries_covered, obituaries_percent,
326 actions_total, actions_covered, actions_percent,
327 special_total, special_covered, special_percent,
328 location_html, arb_msg_html, object_html, hints_html,
329 class_html, turn_html, obituaries_html, actions_html, special_html