Expand scope of coverage_dungeon.py - actions and obituaries
[open-adventure.git] / tests / coverage_dungeon.py
1 #!/usr/bin/env python
2
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.
6 #
7 # Currently, only the location descriptions, arbitrary messages, object
8 # descriptions, hints, classes and turn thrusholds are supported. This will 
9 # be expanded in the future.
10
11 import os
12 import yaml
13 import re
14
15 test_dir = "."
16 yaml_name = "../adventure.yaml"
17 html_template_path = "coverage_dungeon.html.tpl"
18 html_output_path = "../coverage/adventure.yaml.html"
19
20 location_row = """
21     <tr>
22         <td class="coverFile">{}</td>
23         <td class="{}">&nbsp;</td>
24         <td class="{}">&nbsp;</td>
25     </tr>
26 """
27
28 arb_msg_row = """
29     <tr>
30         <td class="coverFile">{}</td>
31         <td class="{}">&nbsp;</td>
32     </tr>
33 """
34
35 object_row = """
36     <tr>
37         <td class="coverFile">{}</td>
38         <td class="{}">&nbsp;</td>
39     </tr>
40 """
41
42 def search(needle, haystack):
43     # Search for needle in haystack, first escaping needle for regex, then
44     # replacing %s, %d, etc. with regex wildcards, so the variable messages
45     # within the dungeon definition will actually match
46     needle = re.escape(needle) \
47              .replace("\%S", ".*") \
48              .replace("\%s", ".*") \
49              .replace("\%d", ".*") \
50              .replace("\%V", ".*")
51
52     return re.search(needle, haystack)
53
54 def loc_coverage(locations, text):
55     for locname, loc in locations:
56         if loc["description"]["long"] == None or loc["description"]["long"] == '':
57             loc["description"]["long"] = True
58         if loc["description"]["long"] != True:
59             if search(loc["description"]["long"], text):
60                 loc["description"]["long"] = True
61         if loc["description"]["short"] == None or loc["description"]["short"] == '':
62             loc["description"]["short"] = True
63         if loc["description"]["short"] != True:
64             if search(loc["description"]["short"], text):
65                 loc["description"]["short"] = True
66
67 def arb_coverage(arb_msgs, text):
68     for i, msg in enumerate(arb_msgs):
69         (msg_name, msg_text) = msg
70         if msg_text == None or msg_text == '':
71             arb_msgs[i] = (msg_name, True)
72         elif msg_text != True:
73             if search(msg_text, text):
74                 arb_msgs[i] = (msg_name, True)
75
76 def obj_coverage(objects, text):
77     for i, objouter in enumerate(objects):
78         (obj_name, obj) = objouter
79         if obj["descriptions"]:
80             for j, desc in enumerate(obj["descriptions"]):
81                 if desc == None or desc == '':
82                     obj["descriptions"][j] = True
83                     objects[i] = (obj_name, obj)
84                 elif desc != True:
85                     if search(desc, text):
86                         obj["descriptions"][j] = True
87                         objects[i] = (obj_name, obj)
88
89 def hint_coverage(hints, text):
90     for name, hint in hints:
91         if hint["question"] != True:
92             if search(hint["question"], text):
93                 hint["question"] = True
94         if hint["hint"] != True:
95             if search(hint["hint"], text):
96                 hint["hint"] = True
97
98 def special_coverage(specials, text):
99     for name, special in specials:
100         if special["message"] == None:
101             special["message"] = True
102         if special["message"] != True:
103             if search(special["message"], text):
104                 special["message"] = True
105
106 def threshold_coverage(classes, text):
107     for i, msg in enumerate(classes):
108         if msg["message"] == None:
109             msg["message"] = True
110         elif msg["message"] != True:
111             if search(msg["message"], text):
112                 msg["message"] = True
113
114 def obit_coverage(obituaries, text):
115     for i, obit in enumerate(obituaries):
116         if obit["query"] != True:
117             if search(obit["query"], text):
118                 obit["query"] = True
119         if obit["yes_response"] != True:
120             if search(obit["yes_response"], text):
121                 obit["yes_response"] = True
122
123 def actions_coverage(actions, text):
124     for name, action in actions:
125         if action["message"] == None:
126             action["message"] = True
127         if action["message"] != True:
128             if search(action["message"], text):
129                 action["message"] = True
130
131 if __name__ == "__main__":
132     with open(yaml_name, "r") as f:
133         db = yaml.load(f)
134
135     with open(html_template_path, "r") as f:
136         html_template = f.read()
137
138     motions = db["motions"]
139     locations = db["locations"]
140     arb_msgs = db["arbitrary_messages"]
141     objects = db["objects"]
142     hintsraw = db["hints"]
143     classes = db["classes"]
144     turn_thresholds = db["turn_thresholds"]
145     obituaries = db["obituaries"]
146     actions = db["actions"]
147     specials = db["specials"]
148
149     hints = []
150     for hint in hintsraw:
151         hints.append((hint["hint"]["name"], {"question" : hint["hint"]["question"],"hint" : hint["hint"]["hint"]}))
152
153     text = ""
154     for filename in os.listdir(test_dir):
155         if filename.endswith(".chk"):
156             with open(filename, "r") as chk:
157                 text = chk.read()
158                 loc_coverage(locations, text)
159                 arb_coverage(arb_msgs, text)
160                 obj_coverage(objects, text)
161                 hint_coverage(hints, text)
162                 threshold_coverage(classes, text)
163                 threshold_coverage(turn_thresholds, text)
164                 obit_coverage(obituaries, text)
165                 actions_coverage(actions, text)
166                 special_coverage(specials, text)
167
168     location_html = ""
169     location_total = len(locations) * 2
170     location_covered = 0
171     locations.sort()
172     for locouter in locations:
173         locname = locouter[0]
174         loc = locouter[1]
175         if loc["description"]["long"] != True:
176             long_success = "uncovered"
177         else:
178             long_success = "covered"
179             location_covered += 1
180
181         if loc["description"]["short"] != True:
182             short_success = "uncovered"
183         else:
184             short_success = "covered"
185             location_covered += 1
186
187         location_html += location_row.format(locname, long_success, short_success)
188     location_percent = round((location_covered / float(location_total)) * 100, 1)
189
190     arb_msgs.sort()
191     arb_msg_html = ""
192     arb_total = len(arb_msgs)
193     arb_covered = 0
194     for name, msg in arb_msgs:
195         if msg != True:
196             success = "uncovered"
197         else:
198             success = "covered"
199             arb_covered += 1
200         arb_msg_html += arb_msg_row.format(name, success)
201     arb_percent = round((arb_covered / float(arb_total)) * 100, 1)
202
203     object_html = ""
204     objects_total = 0
205     objects_covered = 0
206     objects.sort()
207     for (obj_name, obj) in objects:
208         if obj["descriptions"]:
209             for j, desc in enumerate(obj["descriptions"]):
210                 objects_total += 1
211                 if desc != True:
212                     success = "uncovered"
213                 else:
214                     success = "covered"
215                     objects_covered += 1
216                 object_html += object_row.format("%s[%d]" % (obj_name, j), success)
217     objects_percent = round((objects_covered / float(objects_total)) * 100, 1)
218
219     hints.sort()
220     hints_html = "";
221     hints_total = len(hints) * 2
222     hints_covered = 0
223     for name, hint in hints:
224         if hint["question"] != True:
225             question_success = "uncovered"
226         else:
227             question_success = "covered"
228             hints_covered += 1
229         if hint["hint"] != True:
230             hint_success = "uncovered"
231         else:
232             hint_success = "covered"
233             hints_covered += 1
234         hints_html += location_row.format(name, question_success, hint_success)
235     hints_percent = round((hints_covered / float(hints_total)) * 100, 1)
236
237     class_html = ""
238     class_total = len(classes)
239     class_covered = 0
240     for name, msg in enumerate(classes):
241         if msg["message"] != True:
242             success = "uncovered"
243         else:
244             success = "covered"
245             class_covered += 1
246         class_html += arb_msg_row.format(msg["threshold"], success)
247     class_percent = round((class_covered / float(class_total)) * 100, 1)
248
249     turn_html = ""
250     turn_total = len(turn_thresholds)
251     turn_covered = 0
252     for name, msg in enumerate(turn_thresholds):
253         if msg["message"] != True:
254             success = "uncovered"
255         else:
256             success = "covered"
257             turn_covered += 1
258         turn_html += arb_msg_row.format(msg["threshold"], success)
259     turn_percent = round((turn_covered / float(turn_total)) * 100, 1)
260
261     obituaries_html = "";
262     obituaries_total = len(obituaries) * 2
263     obituaries_covered = 0
264     for i, obit in enumerate(obituaries):
265         if obit["query"] != True:
266             query_success = "uncovered"
267         else:
268             query_success = "covered"
269             obituaries_covered += 1
270         if obit["yes_response"] != True:
271             obit_success = "uncovered"
272         else:
273             obit_success = "covered"
274             obituaries_covered += 1
275         obituaries_html += location_row.format(i, query_success, obit_success)
276     obituaries_percent = round((obituaries_covered / float(obituaries_total)) * 100, 1)
277
278     actions.sort()
279     actions_html = "";
280     actions_total = len(actions)
281     actions_covered = 0
282     for name, action in actions:
283         if action["message"] != True:
284             success = "uncovered"
285         else:
286             success = "covered"
287             actions_covered += 1
288         actions_html += arb_msg_row.format(name, success)
289     actions_percent = round((actions_covered / float(actions_total)) * 100, 1)
290
291     special_html = ""
292     special_total = len(specials)
293     special_covered = 0
294     for name, special in specials:
295         if special["message"] != True:
296             success = "uncovered"
297         else:
298             success = "covered"
299             special_covered += 1
300         special_html += arb_msg_row.format(name, success)
301     special_percent = round((special_covered / float(special_total)) * 100, 1)
302
303     # output some quick report stats
304     print("\nadventure.yaml coverage rate:")
305     print("  locations..........: {}% covered ({} of {})".format(location_percent, location_covered, location_total))
306     print("  arbitrary_messages.: {}% covered ({} of {})".format(arb_percent, arb_covered, arb_total))
307     print("  objects............: {}% covered ({} of {})".format(objects_percent, objects_covered, objects_total))
308     print("  hints..............: {}% covered ({} of {})".format(hints_percent, hints_covered, hints_total))
309     print("  classes............: {}% covered ({} of {})".format(class_percent, class_covered, class_total))
310     print("  turn_thresholds....: {}% covered ({} of {})".format(turn_percent, turn_covered, turn_total))
311     print("  obituaries.........: {}% covered ({} of {})".format(obituaries_percent, obituaries_covered, obituaries_total))
312     print("  actions............: {}% covered ({} of {})".format(actions_percent, actions_covered, actions_total))
313     print("  specials...........: {}% covered ({} of {})".format(special_percent, special_covered, special_total))
314
315     # render HTML report
316     with open(html_output_path, "w") as f:
317         f.write(html_template.format(
318                 location_total, location_covered, location_percent,
319                 arb_total, arb_covered, arb_percent,
320                 objects_total, objects_covered, objects_percent,
321                 hints_total, hints_covered, hints_percent,
322                 class_total, class_covered, class_percent,
323                 turn_total, turn_covered, turn_percent,
324                 obituaries_total, obituaries_covered, obituaries_percent,
325                 actions_total, actions_covered, actions_percent,
326                 special_total, special_covered, special_percent,
327                 location_html, arb_msg_html, object_html, hints_html, 
328                 class_html, turn_html, obituaries_html, actions_html, special_html
329         ))