Statistics
| Branch: | Tag: | Revision:

root / test / docs_unittest.py @ 415feb2e

History | View | Annotate | Download (8.8 kB)

1 3f991867 Michael Hanselmann
#!/usr/bin/python
2 3f991867 Michael Hanselmann
#
3 3f991867 Michael Hanselmann
4 3f991867 Michael Hanselmann
# Copyright (C) 2009 Google Inc.
5 3f991867 Michael Hanselmann
#
6 3f991867 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 3f991867 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 3f991867 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 3f991867 Michael Hanselmann
# (at your option) any later version.
10 3f991867 Michael Hanselmann
#
11 3f991867 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 3f991867 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 3f991867 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 3f991867 Michael Hanselmann
# General Public License for more details.
15 3f991867 Michael Hanselmann
#
16 3f991867 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 3f991867 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 3f991867 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 3f991867 Michael Hanselmann
# 02110-1301, USA.
20 3f991867 Michael Hanselmann
21 3f991867 Michael Hanselmann
22 3f991867 Michael Hanselmann
"""Script for unittesting documentation"""
23 3f991867 Michael Hanselmann
24 3f991867 Michael Hanselmann
import unittest
25 3f991867 Michael Hanselmann
import re
26 8497c267 Michael Hanselmann
import itertools
27 8497c267 Michael Hanselmann
import operator
28 3f991867 Michael Hanselmann
29 36bf7973 Michael Hanselmann
from ganeti import _autoconf
30 3f991867 Michael Hanselmann
from ganeti import utils
31 3f991867 Michael Hanselmann
from ganeti import cmdlib
32 e948770c Michael Hanselmann
from ganeti import build
33 3af47e13 Michael Hanselmann
from ganeti import compat
34 83a2da0f Michael Hanselmann
from ganeti import mcpu
35 8497c267 Michael Hanselmann
from ganeti import opcodes
36 8497c267 Michael Hanselmann
from ganeti import constants
37 8497c267 Michael Hanselmann
from ganeti.rapi import baserlib
38 8497c267 Michael Hanselmann
from ganeti.rapi import rlib2
39 bf968b7f Michael Hanselmann
from ganeti.rapi import connector
40 3f991867 Michael Hanselmann
41 3f991867 Michael Hanselmann
import testutils
42 3f991867 Michael Hanselmann
43 3f991867 Michael Hanselmann
44 3af47e13 Michael Hanselmann
VALID_URI_RE = re.compile(r"^[-/a-z0-9]*$")
45 3af47e13 Michael Hanselmann
46 8497c267 Michael Hanselmann
RAPI_OPCODE_EXCLUDE = frozenset([
47 8497c267 Michael Hanselmann
  # Not yet implemented
48 8497c267 Michael Hanselmann
  opcodes.OpBackupQuery,
49 8497c267 Michael Hanselmann
  opcodes.OpBackupRemove,
50 8497c267 Michael Hanselmann
  opcodes.OpClusterConfigQuery,
51 8497c267 Michael Hanselmann
  opcodes.OpClusterRepairDiskSizes,
52 8497c267 Michael Hanselmann
  opcodes.OpClusterVerify,
53 8497c267 Michael Hanselmann
  opcodes.OpClusterVerifyDisks,
54 8497c267 Michael Hanselmann
  opcodes.OpInstanceChangeGroup,
55 8497c267 Michael Hanselmann
  opcodes.OpInstanceMove,
56 8497c267 Michael Hanselmann
  opcodes.OpNodeQueryvols,
57 8497c267 Michael Hanselmann
  opcodes.OpOobCommand,
58 8497c267 Michael Hanselmann
  opcodes.OpTagsSearch,
59 fb926117 Andrea Spadaccini
  opcodes.OpClusterActivateMasterIp,
60 fb926117 Andrea Spadaccini
  opcodes.OpClusterDeactivateMasterIp,
61 8497c267 Michael Hanselmann
62 8497c267 Michael Hanselmann
  # Difficult if not impossible
63 8497c267 Michael Hanselmann
  opcodes.OpClusterDestroy,
64 8497c267 Michael Hanselmann
  opcodes.OpClusterPostInit,
65 8497c267 Michael Hanselmann
  opcodes.OpClusterRename,
66 8497c267 Michael Hanselmann
  opcodes.OpNodeAdd,
67 8497c267 Michael Hanselmann
  opcodes.OpNodeRemove,
68 8497c267 Michael Hanselmann
69 8497c267 Michael Hanselmann
  # Helper opcodes (e.g. submitted by LUs)
70 8497c267 Michael Hanselmann
  opcodes.OpClusterVerifyConfig,
71 8497c267 Michael Hanselmann
  opcodes.OpClusterVerifyGroup,
72 8497c267 Michael Hanselmann
  opcodes.OpGroupEvacuate,
73 8497c267 Michael Hanselmann
  opcodes.OpGroupVerifyDisks,
74 8497c267 Michael Hanselmann
75 8497c267 Michael Hanselmann
  # Test opcodes
76 8497c267 Michael Hanselmann
  opcodes.OpTestAllocator,
77 8497c267 Michael Hanselmann
  opcodes.OpTestDelay,
78 8497c267 Michael Hanselmann
  opcodes.OpTestDummy,
79 8497c267 Michael Hanselmann
  opcodes.OpTestJqueue,
80 8497c267 Michael Hanselmann
  ])
81 8497c267 Michael Hanselmann
82 3af47e13 Michael Hanselmann
83 bf317058 Michael Hanselmann
def _ReadDocFile(filename):
84 bf317058 Michael Hanselmann
  return utils.ReadFile("%s/doc/%s" %
85 bf317058 Michael Hanselmann
                        (testutils.GetSourceDir(), filename))
86 bf317058 Michael Hanselmann
87 3f991867 Michael Hanselmann
88 1315b792 Michael Hanselmann
class TestHooksDocs(unittest.TestCase):
89 1315b792 Michael Hanselmann
  def test(self):
90 3f991867 Michael Hanselmann
    """Check whether all hooks are documented.
91 3f991867 Michael Hanselmann

92 3f991867 Michael Hanselmann
    """
93 bf317058 Michael Hanselmann
    hooksdoc = _ReadDocFile("hooks.rst")
94 3f991867 Michael Hanselmann
95 83a2da0f Michael Hanselmann
    # Reverse mapping from LU to opcode
96 83a2da0f Michael Hanselmann
    lu2opcode = dict((lu, op)
97 83a2da0f Michael Hanselmann
                     for (op, lu) in mcpu.Processor.DISPATCH_TABLE.items())
98 83a2da0f Michael Hanselmann
    assert len(lu2opcode) == len(mcpu.Processor.DISPATCH_TABLE), \
99 83a2da0f Michael Hanselmann
      "Found duplicate entries"
100 83a2da0f Michael Hanselmann
101 3f991867 Michael Hanselmann
    for name in dir(cmdlib):
102 3f991867 Michael Hanselmann
      obj = getattr(cmdlib, name)
103 3f991867 Michael Hanselmann
104 3f991867 Michael Hanselmann
      if (isinstance(obj, type) and
105 3f991867 Michael Hanselmann
          issubclass(obj, cmdlib.LogicalUnit) and
106 3f991867 Michael Hanselmann
          hasattr(obj, "HPATH")):
107 83a2da0f Michael Hanselmann
        self._CheckHook(name, obj, hooksdoc, lu2opcode)
108 83a2da0f Michael Hanselmann
109 83a2da0f Michael Hanselmann
  def _CheckHook(self, name, lucls, hooksdoc, lu2opcode):
110 83a2da0f Michael Hanselmann
    opcls = lu2opcode.get(lucls, None)
111 3f991867 Michael Hanselmann
112 3f991867 Michael Hanselmann
    if lucls.HTYPE is None:
113 3f991867 Michael Hanselmann
      return
114 3f991867 Michael Hanselmann
115 3f991867 Michael Hanselmann
    # TODO: Improve this test (e.g. find hooks documented but no longer
116 3f991867 Michael Hanselmann
    # existing)
117 3f991867 Michael Hanselmann
118 83a2da0f Michael Hanselmann
    if opcls:
119 83a2da0f Michael Hanselmann
      self.assertTrue(re.findall("^%s$" % re.escape(opcls.OP_ID),
120 83a2da0f Michael Hanselmann
                                 hooksdoc, re.M),
121 83a2da0f Michael Hanselmann
                      msg=("Missing hook documentation for %s" %
122 83a2da0f Michael Hanselmann
                           (opcls.OP_ID)))
123 83a2da0f Michael Hanselmann
124 3f991867 Michael Hanselmann
    pattern = r"^:directory:\s*%s\s*$" % re.escape(lucls.HPATH)
125 3f991867 Michael Hanselmann
126 3f991867 Michael Hanselmann
    self.assert_(re.findall(pattern, hooksdoc, re.M),
127 3f991867 Michael Hanselmann
                 msg=("Missing documentation for hook %s/%s" %
128 3f991867 Michael Hanselmann
                      (lucls.HTYPE, lucls.HPATH)))
129 3f991867 Michael Hanselmann
130 1315b792 Michael Hanselmann
131 1315b792 Michael Hanselmann
class TestRapiDocs(unittest.TestCase):
132 b58a4d16 Michael Hanselmann
  def _CheckRapiResource(self, uri, fixup, handler):
133 b58a4d16 Michael Hanselmann
    docline = "%s resource." % uri
134 b58a4d16 Michael Hanselmann
    self.assertEqual(handler.__doc__.splitlines()[0].strip(), docline,
135 b58a4d16 Michael Hanselmann
                     msg=("First line of %r's docstring is not %r" %
136 b58a4d16 Michael Hanselmann
                          (handler, docline)))
137 b58a4d16 Michael Hanselmann
138 3af47e13 Michael Hanselmann
    # Apply fixes before testing
139 3af47e13 Michael Hanselmann
    for (rx, value) in fixup.items():
140 3af47e13 Michael Hanselmann
      uri = rx.sub(value, uri)
141 3af47e13 Michael Hanselmann
142 3af47e13 Michael Hanselmann
    self.assertTrue(VALID_URI_RE.match(uri), msg="Invalid URI %r" % uri)
143 3f991867 Michael Hanselmann
144 1315b792 Michael Hanselmann
  def test(self):
145 bf968b7f Michael Hanselmann
    """Check whether all RAPI resources are documented.
146 bf968b7f Michael Hanselmann

147 bf968b7f Michael Hanselmann
    """
148 bf317058 Michael Hanselmann
    rapidoc = _ReadDocFile("rapi.rst")
149 bf968b7f Michael Hanselmann
150 3af47e13 Michael Hanselmann
    node_name = re.escape("[node_name]")
151 3af47e13 Michael Hanselmann
    instance_name = re.escape("[instance_name]")
152 3af47e13 Michael Hanselmann
    group_name = re.escape("[group_name]")
153 3af47e13 Michael Hanselmann
    job_id = re.escape("[job_id]")
154 3af47e13 Michael Hanselmann
    disk_index = re.escape("[disk_index]")
155 1c7fd467 Michael Hanselmann
    query_res = re.escape("[resource]")
156 3af47e13 Michael Hanselmann
157 3af47e13 Michael Hanselmann
    resources = connector.GetHandlers(node_name, instance_name, group_name,
158 1c7fd467 Michael Hanselmann
                                      job_id, disk_index, query_res)
159 3af47e13 Michael Hanselmann
160 d50a2223 Michael Hanselmann
    handler_dups = utils.FindDuplicates(resources.values())
161 d50a2223 Michael Hanselmann
    self.assertFalse(handler_dups,
162 d50a2223 Michael Hanselmann
                     msg=("Resource handlers used more than once: %r" %
163 d50a2223 Michael Hanselmann
                          handler_dups))
164 d50a2223 Michael Hanselmann
165 3af47e13 Michael Hanselmann
    uri_check_fixup = {
166 3af47e13 Michael Hanselmann
      re.compile(node_name): "node1examplecom",
167 3af47e13 Michael Hanselmann
      re.compile(instance_name): "inst1examplecom",
168 3af47e13 Michael Hanselmann
      re.compile(group_name): "group4440",
169 3af47e13 Michael Hanselmann
      re.compile(job_id): "9409",
170 3af47e13 Michael Hanselmann
      re.compile(disk_index): "123",
171 1c7fd467 Michael Hanselmann
      re.compile(query_res): "lock",
172 3af47e13 Michael Hanselmann
      }
173 bf968b7f Michael Hanselmann
174 3af47e13 Michael Hanselmann
    assert compat.all(VALID_URI_RE.match(value)
175 3af47e13 Michael Hanselmann
                      for value in uri_check_fixup.values()), \
176 3af47e13 Michael Hanselmann
           "Fixup values must be valid URIs, too"
177 bf968b7f Michael Hanselmann
178 bf968b7f Michael Hanselmann
    titles = []
179 bf968b7f Michael Hanselmann
180 bf968b7f Michael Hanselmann
    prevline = None
181 bf968b7f Michael Hanselmann
    for line in rapidoc.splitlines():
182 bf968b7f Michael Hanselmann
      if re.match(r"^\++$", line):
183 bf968b7f Michael Hanselmann
        titles.append(prevline)
184 bf968b7f Michael Hanselmann
185 bf968b7f Michael Hanselmann
      prevline = line
186 bf968b7f Michael Hanselmann
187 132cdb87 Michael Hanselmann
    prefix_exception = frozenset(["/", "/version", "/2"])
188 2c0be3d0 Michael Hanselmann
189 bf968b7f Michael Hanselmann
    undocumented = []
190 d50a2223 Michael Hanselmann
    used_uris = []
191 bf968b7f Michael Hanselmann
192 bf968b7f Michael Hanselmann
    for key, handler in resources.iteritems():
193 bf968b7f Michael Hanselmann
      # Regex objects
194 bf968b7f Michael Hanselmann
      if hasattr(key, "match"):
195 2c0be3d0 Michael Hanselmann
        self.assert_(key.pattern.startswith("^/2/"),
196 2c0be3d0 Michael Hanselmann
                     msg="Pattern %r does not start with '^/2/'" % key.pattern)
197 3af47e13 Michael Hanselmann
        self.assertEqual(key.pattern[-1], "$")
198 2c0be3d0 Michael Hanselmann
199 bf968b7f Michael Hanselmann
        found = False
200 bf968b7f Michael Hanselmann
        for title in titles:
201 3af47e13 Michael Hanselmann
          if title.startswith("``") and title.endswith("``"):
202 3af47e13 Michael Hanselmann
            uri = title[2:-2]
203 3af47e13 Michael Hanselmann
            if key.match(uri):
204 b58a4d16 Michael Hanselmann
              self._CheckRapiResource(uri, uri_check_fixup, handler)
205 d50a2223 Michael Hanselmann
              used_uris.append(uri)
206 3af47e13 Michael Hanselmann
              found = True
207 3af47e13 Michael Hanselmann
              break
208 bf968b7f Michael Hanselmann
209 bf968b7f Michael Hanselmann
        if not found:
210 bf968b7f Michael Hanselmann
          # TODO: Find better way of identifying resource
211 2c0be3d0 Michael Hanselmann
          undocumented.append(key.pattern)
212 2c0be3d0 Michael Hanselmann
213 2c0be3d0 Michael Hanselmann
      else:
214 2c0be3d0 Michael Hanselmann
        self.assert_(key.startswith("/2/") or key in prefix_exception,
215 2c0be3d0 Michael Hanselmann
                     msg="Path %r does not start with '/2/'" % key)
216 bf968b7f Michael Hanselmann
217 3af47e13 Michael Hanselmann
        if ("``%s``" % key) in titles:
218 b58a4d16 Michael Hanselmann
          self._CheckRapiResource(key, {}, handler)
219 d50a2223 Michael Hanselmann
          used_uris.append(key)
220 3af47e13 Michael Hanselmann
        else:
221 2c0be3d0 Michael Hanselmann
          undocumented.append(key)
222 bf968b7f Michael Hanselmann
223 bf968b7f Michael Hanselmann
    self.failIf(undocumented,
224 bf968b7f Michael Hanselmann
                msg=("Missing RAPI resource documentation for %s" %
225 ab3e6da8 Iustin Pop
                     utils.CommaJoin(undocumented)))
226 bf968b7f Michael Hanselmann
227 d50a2223 Michael Hanselmann
    uri_dups = utils.FindDuplicates(used_uris)
228 d50a2223 Michael Hanselmann
    self.failIf(uri_dups,
229 d50a2223 Michael Hanselmann
                msg=("URIs matched by more than one resource: %s" %
230 d50a2223 Michael Hanselmann
                     utils.CommaJoin(uri_dups)))
231 d50a2223 Michael Hanselmann
232 8497c267 Michael Hanselmann
    self._FindRapiMissing(resources.values())
233 8497c267 Michael Hanselmann
    self._CheckTagHandlers(resources.values())
234 8497c267 Michael Hanselmann
235 8497c267 Michael Hanselmann
  def _FindRapiMissing(self, handlers):
236 8497c267 Michael Hanselmann
    used = frozenset(itertools.chain(*map(baserlib.GetResourceOpcodes,
237 8497c267 Michael Hanselmann
                                          handlers)))
238 8497c267 Michael Hanselmann
239 8497c267 Michael Hanselmann
    unexpected = used & RAPI_OPCODE_EXCLUDE
240 8497c267 Michael Hanselmann
    self.assertFalse(unexpected,
241 8497c267 Michael Hanselmann
      msg=("Found RAPI resources for excluded opcodes: %s" %
242 8497c267 Michael Hanselmann
           utils.CommaJoin(_GetOpIds(unexpected))))
243 8497c267 Michael Hanselmann
244 8497c267 Michael Hanselmann
    missing = (frozenset(opcodes.OP_MAPPING.values()) - used -
245 8497c267 Michael Hanselmann
               RAPI_OPCODE_EXCLUDE)
246 8497c267 Michael Hanselmann
    self.assertFalse(missing,
247 8497c267 Michael Hanselmann
      msg=("Missing RAPI resources for opcodes: %s" %
248 8497c267 Michael Hanselmann
           utils.CommaJoin(_GetOpIds(missing))))
249 8497c267 Michael Hanselmann
250 8497c267 Michael Hanselmann
  def _CheckTagHandlers(self, handlers):
251 8497c267 Michael Hanselmann
    tag_handlers = filter(lambda x: issubclass(x, rlib2._R_Tags), handlers)
252 8497c267 Michael Hanselmann
    self.assertEqual(frozenset(map(operator.attrgetter("TAG_LEVEL"),
253 8497c267 Michael Hanselmann
                                   tag_handlers)),
254 8497c267 Michael Hanselmann
                     constants.VALID_TAG_TYPES)
255 8497c267 Michael Hanselmann
256 8497c267 Michael Hanselmann
257 8497c267 Michael Hanselmann
def _GetOpIds(ops):
258 8497c267 Michael Hanselmann
  """Returns C{OP_ID} for all opcodes in passed sequence.
259 8497c267 Michael Hanselmann

260 8497c267 Michael Hanselmann
  """
261 8497c267 Michael Hanselmann
  return sorted(opcls.OP_ID for opcls in ops)
262 8497c267 Michael Hanselmann
263 bf968b7f Michael Hanselmann
264 36bf7973 Michael Hanselmann
class TestManpages(unittest.TestCase):
265 36bf7973 Michael Hanselmann
  """Manpage tests"""
266 36bf7973 Michael Hanselmann
267 36bf7973 Michael Hanselmann
  @staticmethod
268 36bf7973 Michael Hanselmann
  def _ReadManFile(name):
269 6be8e2bf Iustin Pop
    return utils.ReadFile("%s/man/%s.rst" %
270 36bf7973 Michael Hanselmann
                          (testutils.GetSourceDir(), name))
271 36bf7973 Michael Hanselmann
272 36bf7973 Michael Hanselmann
  @staticmethod
273 36bf7973 Michael Hanselmann
  def _LoadScript(name):
274 e948770c Michael Hanselmann
    return build.LoadModule("scripts/%s" % name)
275 36bf7973 Michael Hanselmann
276 36bf7973 Michael Hanselmann
  def test(self):
277 36bf7973 Michael Hanselmann
    for script in _autoconf.GNT_SCRIPTS:
278 36bf7973 Michael Hanselmann
      self._CheckManpage(script,
279 36bf7973 Michael Hanselmann
                         self._ReadManFile(script),
280 36bf7973 Michael Hanselmann
                         self._LoadScript(script).commands.keys())
281 36bf7973 Michael Hanselmann
282 36bf7973 Michael Hanselmann
  def _CheckManpage(self, script, mantext, commands):
283 36bf7973 Michael Hanselmann
    missing = []
284 36bf7973 Michael Hanselmann
285 36bf7973 Michael Hanselmann
    for cmd in commands:
286 6be8e2bf Iustin Pop
      pattern = r"^(\| )?\*\*%s\*\*" % re.escape(cmd)
287 6be8e2bf Iustin Pop
      if not re.findall(pattern, mantext, re.DOTALL | re.MULTILINE):
288 36bf7973 Michael Hanselmann
        missing.append(cmd)
289 36bf7973 Michael Hanselmann
290 36bf7973 Michael Hanselmann
    self.failIf(missing,
291 36bf7973 Michael Hanselmann
                msg=("Manpage for '%s' missing documentation for %s" %
292 ab3e6da8 Iustin Pop
                     (script, utils.CommaJoin(missing))))
293 36bf7973 Michael Hanselmann
294 36bf7973 Michael Hanselmann
295 3f991867 Michael Hanselmann
if __name__ == "__main__":
296 25231ec5 Michael Hanselmann
  testutils.GanetiTestProgram()