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