Expand scope of coverage_dungeon.py - specials
[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
115 if __name__ == "__main__":
116     with open(yaml_name, "r") as f:
117         db = yaml.load(f)
118
119     with open(html_template_path, "r") as f:
120         html_template = f.read()
121
122     locations = db["locations"]
123     arb_msgs = db["arbitrary_messages"]
124     objects = db["objects"]
125     hintsraw = db["hints"]
126     classes = db["classes"]
127     turn_thresholds = db["turn_thresholds"]
128     specials = db["specials"]
129
130     hints = []
131     for hint in hintsraw:
132         hints.append((hint["hint"]["name"], {"question" : hint["hint"]["question"],"hint" : hint["hint"]["hint"]}))
133
134     text = ""
135     for filename in os.listdir(test_dir):
136         if filename.endswith(".chk"):
137             with open(filename, "r") as chk:
138                 text = chk.read()
139                 loc_coverage(locations, text)
140                 arb_coverage(arb_msgs, text)
141                 obj_coverage(objects, text)
142                 hint_coverage(hints, text)
143                 threshold_coverage(classes, text)
144                 threshold_coverage(turn_thresholds, text)
145                 special_coverage(specials, text)
146
147     location_html = ""
148     location_total = len(locations) * 2
149     location_covered = 0
150     locations.sort()
151     for locouter in locations:
152         locname = locouter[0]
153         loc = locouter[1]
154         if loc["description"]["long"] != True:
155             long_success = "uncovered"
156         else:
157             long_success = "covered"
158             location_covered += 1
159
160         if loc["description"]["short"] != True:
161             short_success = "uncovered"
162         else:
163             short_success = "covered"
164             location_covered += 1
165
166         location_html += location_row.format(locname, long_success, short_success)
167     location_percent = round((location_covered / float(location_total)) * 100, 1)
168
169     arb_msgs.sort()
170     arb_msg_html = ""
171     arb_total = len(arb_msgs)
172     arb_covered = 0
173     for name, msg in arb_msgs:
174         if msg != True:
175             success = "uncovered"
176         else:
177             success = "covered"
178             arb_covered += 1
179         arb_msg_html += arb_msg_row.format(name, success)
180     arb_percent = round((arb_covered / float(arb_total)) * 100, 1)
181
182     object_html = ""
183     objects_total = 0
184     objects_covered = 0
185     objects.sort()
186     for (obj_name, obj) in objects:
187         if obj["descriptions"]:
188             for j, desc in enumerate(obj["descriptions"]):
189                 objects_total += 1
190                 if desc != True:
191                     success = "uncovered"
192                 else:
193                     success = "covered"
194                     objects_covered += 1
195                 object_html += object_row.format("%s[%d]" % (obj_name, j), success)
196     objects_percent = round((objects_covered / float(objects_total)) * 100, 1)
197
198     hints.sort()
199     hints_html = "";
200     hints_total = len(hints) * 2
201     hints_covered = 0
202     for name, hint in hints:
203         if hint["question"] != True:
204             question_success = "uncovered"
205         else:
206             question_success = "covered"
207             hints_covered += 1
208         if hint["hint"] != True:
209             hint_success = "uncovered"
210         else:
211             hint_success = "covered"
212             hints_covered += 1
213         hints_html += location_row.format(name, question_success, hint_success)
214     hints_percent = round((hints_covered / float(hints_total)) * 100, 1)
215
216     class_html = ""
217     class_total = len(classes)
218     class_covered = 0
219     for name, msg in enumerate(classes):
220         if msg["message"] != True:
221             success = "uncovered"
222         else:
223             success = "covered"
224             class_covered += 1
225         class_html += arb_msg_row.format(msg["threshold"], success)
226     class_percent = round((class_covered / float(class_total)) * 100, 1)
227
228     turn_html = ""
229     turn_total = len(turn_thresholds)
230     turn_covered = 0
231     for name, msg in enumerate(turn_thresholds):
232         if msg["message"] != True:
233             success = "uncovered"
234         else:
235             success = "covered"
236             turn_covered += 1
237         turn_html += arb_msg_row.format(msg["threshold"], success)
238     turn_percent = round((turn_covered / float(turn_total)) * 100, 1)
239
240     special_html = ""
241     special_total = len(specials)
242     special_covered = 0
243     for name, special in specials:
244         if special["message"] != True:
245             success = "uncovered"
246         else:
247             success = "covered"
248             special_covered += 1
249         special_html += arb_msg_row.format(name, success)
250     special_percent = round((special_covered / float(special_total)) * 100, 1)
251
252     # output some quick report stats
253     print("\nadventure.yaml coverage rate:")
254     print("  locations..........: {}% covered ({} of {})".format(location_percent, location_covered, location_total))
255     print("  arbitrary_messages.: {}% covered ({} of {})".format(arb_percent, arb_covered, arb_total))
256     print("  objects............: {}% covered ({} of {})".format(objects_percent, objects_covered, objects_total))
257     print("  hints..............: {}% covered ({} of {})".format(hints_percent, hints_covered, hints_total))
258     print("  classes............: {}% covered ({} of {})".format(class_percent, class_covered, class_total))
259     print("  turn_thresholds....: {}% covered ({} of {})".format(turn_percent, turn_covered, turn_total))
260     print("  specials...........: {}% covered ({} of {})".format(special_percent, special_covered, special_total))
261
262     # render HTML report
263     with open(html_output_path, "w") as f:
264         f.write(html_template.format(
265                 location_total, location_covered, location_percent,
266                 arb_total, arb_covered, arb_percent,
267                 objects_total, objects_covered, objects_percent,
268                 hints_total, hints_covered, hints_percent,
269                 class_total, class_covered, class_percent,
270                 turn_total, turn_covered, turn_percent,
271                 special_total, special_covered, special_percent,
272                 location_html, arb_msg_html, object_html, hints_html, 
273                 class_html, turn_html, special_html
274         ))