Add unittest for cli.FormatResultError
[ganeti-local] / test / docs_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2009 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Script for unittesting documentation"""
23
24 import unittest
25 import re
26
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.rapi import connector
33
34 import testutils
35
36
37 VALID_URI_RE = re.compile(r"^[-/a-z0-9]*$")
38
39
40 class TestDocs(unittest.TestCase):
41   """Documentation tests"""
42
43   @staticmethod
44   def _ReadDocFile(filename):
45     return utils.ReadFile("%s/doc/%s" %
46                           (testutils.GetSourceDir(), filename))
47
48   def testHookDocs(self):
49     """Check whether all hooks are documented.
50
51     """
52     hooksdoc = self._ReadDocFile("hooks.rst")
53
54     for name in dir(cmdlib):
55       obj = getattr(cmdlib, name)
56
57       if (isinstance(obj, type) and
58           issubclass(obj, cmdlib.LogicalUnit) and
59           hasattr(obj, "HPATH")):
60         self._CheckHook(name, obj, hooksdoc)
61
62   def _CheckHook(self, name, lucls, hooksdoc):
63     if lucls.HTYPE is None:
64       return
65
66     # TODO: Improve this test (e.g. find hooks documented but no longer
67     # existing)
68
69     pattern = r"^:directory:\s*%s\s*$" % re.escape(lucls.HPATH)
70
71     self.assert_(re.findall(pattern, hooksdoc, re.M),
72                  msg=("Missing documentation for hook %s/%s" %
73                       (lucls.HTYPE, lucls.HPATH)))
74
75   def _CheckRapiResource(self, uri, fixup, handler):
76     docline = "%s resource." % uri
77     self.assertEqual(handler.__doc__.splitlines()[0].strip(), docline,
78                      msg=("First line of %r's docstring is not %r" %
79                           (handler, docline)))
80
81     # Apply fixes before testing
82     for (rx, value) in fixup.items():
83       uri = rx.sub(value, uri)
84
85     self.assertTrue(VALID_URI_RE.match(uri), msg="Invalid URI %r" % uri)
86
87   def testRapiDocs(self):
88     """Check whether all RAPI resources are documented.
89
90     """
91     rapidoc = self._ReadDocFile("rapi.rst")
92
93     node_name = re.escape("[node_name]")
94     instance_name = re.escape("[instance_name]")
95     group_name = re.escape("[group_name]")
96     job_id = re.escape("[job_id]")
97     disk_index = re.escape("[disk_index]")
98
99     resources = connector.GetHandlers(node_name, instance_name, group_name,
100                                       job_id, disk_index)
101
102     handler_dups = utils.FindDuplicates(resources.values())
103     self.assertFalse(handler_dups,
104                      msg=("Resource handlers used more than once: %r" %
105                           handler_dups))
106
107     uri_check_fixup = {
108       re.compile(node_name): "node1examplecom",
109       re.compile(instance_name): "inst1examplecom",
110       re.compile(group_name): "group4440",
111       re.compile(job_id): "9409",
112       re.compile(disk_index): "123",
113       }
114
115     assert compat.all(VALID_URI_RE.match(value)
116                       for value in uri_check_fixup.values()), \
117            "Fixup values must be valid URIs, too"
118
119     titles = []
120
121     prevline = None
122     for line in rapidoc.splitlines():
123       if re.match(r"^\++$", line):
124         titles.append(prevline)
125
126       prevline = line
127
128     prefix_exception = frozenset(["/", "/version", "/2"])
129
130     undocumented = []
131     used_uris = []
132
133     for key, handler in resources.iteritems():
134       # Regex objects
135       if hasattr(key, "match"):
136         self.assert_(key.pattern.startswith("^/2/"),
137                      msg="Pattern %r does not start with '^/2/'" % key.pattern)
138         self.assertEqual(key.pattern[-1], "$")
139
140         found = False
141         for title in titles:
142           if title.startswith("``") and title.endswith("``"):
143             uri = title[2:-2]
144             if key.match(uri):
145               self._CheckRapiResource(uri, uri_check_fixup, handler)
146               used_uris.append(uri)
147               found = True
148               break
149
150         if not found:
151           # TODO: Find better way of identifying resource
152           undocumented.append(key.pattern)
153
154       else:
155         self.assert_(key.startswith("/2/") or key in prefix_exception,
156                      msg="Path %r does not start with '/2/'" % key)
157
158         if ("``%s``" % key) in titles:
159           self._CheckRapiResource(key, {}, handler)
160           used_uris.append(key)
161         else:
162           undocumented.append(key)
163
164     self.failIf(undocumented,
165                 msg=("Missing RAPI resource documentation for %s" %
166                      utils.CommaJoin(undocumented)))
167
168     uri_dups = utils.FindDuplicates(used_uris)
169     self.failIf(uri_dups,
170                 msg=("URIs matched by more than one resource: %s" %
171                      utils.CommaJoin(uri_dups)))
172
173
174 class TestManpages(unittest.TestCase):
175   """Manpage tests"""
176
177   @staticmethod
178   def _ReadManFile(name):
179     return utils.ReadFile("%s/man/%s.rst" %
180                           (testutils.GetSourceDir(), name))
181
182   @staticmethod
183   def _LoadScript(name):
184     return build.LoadModule("scripts/%s" % name)
185
186   def test(self):
187     for script in _autoconf.GNT_SCRIPTS:
188       self._CheckManpage(script,
189                          self._ReadManFile(script),
190                          self._LoadScript(script).commands.keys())
191
192   def _CheckManpage(self, script, mantext, commands):
193     missing = []
194
195     for cmd in commands:
196       pattern = r"^(\| )?\*\*%s\*\*" % re.escape(cmd)
197       if not re.findall(pattern, mantext, re.DOTALL | re.MULTILINE):
198         missing.append(cmd)
199
200     self.failIf(missing,
201                 msg=("Manpage for '%s' missing documentation for %s" %
202                      (script, utils.CommaJoin(missing))))
203
204
205 if __name__ == "__main__":
206   testutils.GanetiTestProgram()