4 # Copyright (C) 2009 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Script for unittesting documentation"""
27 from ganeti import _autoconf
28 from ganeti import utils
29 from ganeti import cmdlib
30 from ganeti import build
31 from ganeti import compat
32 from ganeti import mcpu
33 from ganeti.rapi import connector
38 VALID_URI_RE = re.compile(r"^[-/a-z0-9]*$")
41 class TestDocs(unittest.TestCase):
42 """Documentation tests"""
45 def _ReadDocFile(filename):
46 return utils.ReadFile("%s/doc/%s" %
47 (testutils.GetSourceDir(), filename))
49 def testHookDocs(self):
50 """Check whether all hooks are documented.
53 hooksdoc = self._ReadDocFile("hooks.rst")
55 # Reverse mapping from LU to opcode
56 lu2opcode = dict((lu, op)
57 for (op, lu) in mcpu.Processor.DISPATCH_TABLE.items())
58 assert len(lu2opcode) == len(mcpu.Processor.DISPATCH_TABLE), \
59 "Found duplicate entries"
61 for name in dir(cmdlib):
62 obj = getattr(cmdlib, name)
64 if (isinstance(obj, type) and
65 issubclass(obj, cmdlib.LogicalUnit) and
66 hasattr(obj, "HPATH")):
67 self._CheckHook(name, obj, hooksdoc, lu2opcode)
69 def _CheckHook(self, name, lucls, hooksdoc, lu2opcode):
70 opcls = lu2opcode.get(lucls, None)
72 if lucls.HTYPE is None:
75 # TODO: Improve this test (e.g. find hooks documented but no longer
79 self.assertTrue(re.findall("^%s$" % re.escape(opcls.OP_ID),
81 msg=("Missing hook documentation for %s" %
84 pattern = r"^:directory:\s*%s\s*$" % re.escape(lucls.HPATH)
86 self.assert_(re.findall(pattern, hooksdoc, re.M),
87 msg=("Missing documentation for hook %s/%s" %
88 (lucls.HTYPE, lucls.HPATH)))
90 def _CheckRapiResource(self, uri, fixup, handler):
91 docline = "%s resource." % uri
92 self.assertEqual(handler.__doc__.splitlines()[0].strip(), docline,
93 msg=("First line of %r's docstring is not %r" %
96 # Apply fixes before testing
97 for (rx, value) in fixup.items():
98 uri = rx.sub(value, uri)
100 self.assertTrue(VALID_URI_RE.match(uri), msg="Invalid URI %r" % uri)
102 def testRapiDocs(self):
103 """Check whether all RAPI resources are documented.
106 rapidoc = self._ReadDocFile("rapi.rst")
108 node_name = re.escape("[node_name]")
109 instance_name = re.escape("[instance_name]")
110 group_name = re.escape("[group_name]")
111 job_id = re.escape("[job_id]")
112 disk_index = re.escape("[disk_index]")
113 query_res = re.escape("[resource]")
115 resources = connector.GetHandlers(node_name, instance_name, group_name,
116 job_id, disk_index, query_res)
118 handler_dups = utils.FindDuplicates(resources.values())
119 self.assertFalse(handler_dups,
120 msg=("Resource handlers used more than once: %r" %
124 re.compile(node_name): "node1examplecom",
125 re.compile(instance_name): "inst1examplecom",
126 re.compile(group_name): "group4440",
127 re.compile(job_id): "9409",
128 re.compile(disk_index): "123",
129 re.compile(query_res): "lock",
132 assert compat.all(VALID_URI_RE.match(value)
133 for value in uri_check_fixup.values()), \
134 "Fixup values must be valid URIs, too"
139 for line in rapidoc.splitlines():
140 if re.match(r"^\++$", line):
141 titles.append(prevline)
145 prefix_exception = frozenset(["/", "/version", "/2"])
150 for key, handler in resources.iteritems():
152 if hasattr(key, "match"):
153 self.assert_(key.pattern.startswith("^/2/"),
154 msg="Pattern %r does not start with '^/2/'" % key.pattern)
155 self.assertEqual(key.pattern[-1], "$")
159 if title.startswith("``") and title.endswith("``"):
162 self._CheckRapiResource(uri, uri_check_fixup, handler)
163 used_uris.append(uri)
168 # TODO: Find better way of identifying resource
169 undocumented.append(key.pattern)
172 self.assert_(key.startswith("/2/") or key in prefix_exception,
173 msg="Path %r does not start with '/2/'" % key)
175 if ("``%s``" % key) in titles:
176 self._CheckRapiResource(key, {}, handler)
177 used_uris.append(key)
179 undocumented.append(key)
181 self.failIf(undocumented,
182 msg=("Missing RAPI resource documentation for %s" %
183 utils.CommaJoin(undocumented)))
185 uri_dups = utils.FindDuplicates(used_uris)
186 self.failIf(uri_dups,
187 msg=("URIs matched by more than one resource: %s" %
188 utils.CommaJoin(uri_dups)))
191 class TestManpages(unittest.TestCase):
195 def _ReadManFile(name):
196 return utils.ReadFile("%s/man/%s.rst" %
197 (testutils.GetSourceDir(), name))
200 def _LoadScript(name):
201 return build.LoadModule("scripts/%s" % name)
204 for script in _autoconf.GNT_SCRIPTS:
205 self._CheckManpage(script,
206 self._ReadManFile(script),
207 self._LoadScript(script).commands.keys())
209 def _CheckManpage(self, script, mantext, commands):
213 pattern = r"^(\| )?\*\*%s\*\*" % re.escape(cmd)
214 if not re.findall(pattern, mantext, re.DOTALL | re.MULTILINE):
218 msg=("Manpage for '%s' missing documentation for %s" %
219 (script, utils.CommaJoin(missing))))
222 if __name__ == "__main__":
223 testutils.GanetiTestProgram()