Statistics
| Branch: | Tag: | Revision:

root / test / docs_unittest.py @ 83a2da0f

History | View | Annotate | Download (6.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 3f991867 Michael Hanselmann
27 36bf7973 Michael Hanselmann
from ganeti import _autoconf
28 3f991867 Michael Hanselmann
from ganeti import utils
29 3f991867 Michael Hanselmann
from ganeti import cmdlib
30 e948770c Michael Hanselmann
from ganeti import build
31 3af47e13 Michael Hanselmann
from ganeti import compat
32 83a2da0f Michael Hanselmann
from ganeti import mcpu
33 bf968b7f Michael Hanselmann
from ganeti.rapi import connector
34 3f991867 Michael Hanselmann
35 3f991867 Michael Hanselmann
import testutils
36 3f991867 Michael Hanselmann
37 3f991867 Michael Hanselmann
38 3af47e13 Michael Hanselmann
VALID_URI_RE = re.compile(r"^[-/a-z0-9]*$")
39 3af47e13 Michael Hanselmann
40 3af47e13 Michael Hanselmann
41 3f991867 Michael Hanselmann
class TestDocs(unittest.TestCase):
42 3f991867 Michael Hanselmann
  """Documentation tests"""
43 3f991867 Michael Hanselmann
44 3f991867 Michael Hanselmann
  @staticmethod
45 3f991867 Michael Hanselmann
  def _ReadDocFile(filename):
46 3f991867 Michael Hanselmann
    return utils.ReadFile("%s/doc/%s" %
47 3f991867 Michael Hanselmann
                          (testutils.GetSourceDir(), filename))
48 3f991867 Michael Hanselmann
49 3f991867 Michael Hanselmann
  def testHookDocs(self):
50 3f991867 Michael Hanselmann
    """Check whether all hooks are documented.
51 3f991867 Michael Hanselmann

52 3f991867 Michael Hanselmann
    """
53 3f991867 Michael Hanselmann
    hooksdoc = self._ReadDocFile("hooks.rst")
54 3f991867 Michael Hanselmann
55 83a2da0f Michael Hanselmann
    # Reverse mapping from LU to opcode
56 83a2da0f Michael Hanselmann
    lu2opcode = dict((lu, op)
57 83a2da0f Michael Hanselmann
                     for (op, lu) in mcpu.Processor.DISPATCH_TABLE.items())
58 83a2da0f Michael Hanselmann
    assert len(lu2opcode) == len(mcpu.Processor.DISPATCH_TABLE), \
59 83a2da0f Michael Hanselmann
      "Found duplicate entries"
60 83a2da0f Michael Hanselmann
61 3f991867 Michael Hanselmann
    for name in dir(cmdlib):
62 3f991867 Michael Hanselmann
      obj = getattr(cmdlib, name)
63 3f991867 Michael Hanselmann
64 3f991867 Michael Hanselmann
      if (isinstance(obj, type) and
65 3f991867 Michael Hanselmann
          issubclass(obj, cmdlib.LogicalUnit) and
66 3f991867 Michael Hanselmann
          hasattr(obj, "HPATH")):
67 83a2da0f Michael Hanselmann
        self._CheckHook(name, obj, hooksdoc, lu2opcode)
68 83a2da0f Michael Hanselmann
69 83a2da0f Michael Hanselmann
  def _CheckHook(self, name, lucls, hooksdoc, lu2opcode):
70 83a2da0f Michael Hanselmann
    opcls = lu2opcode.get(lucls, None)
71 3f991867 Michael Hanselmann
72 3f991867 Michael Hanselmann
    if lucls.HTYPE is None:
73 3f991867 Michael Hanselmann
      return
74 3f991867 Michael Hanselmann
75 3f991867 Michael Hanselmann
    # TODO: Improve this test (e.g. find hooks documented but no longer
76 3f991867 Michael Hanselmann
    # existing)
77 3f991867 Michael Hanselmann
78 83a2da0f Michael Hanselmann
    if opcls:
79 83a2da0f Michael Hanselmann
      self.assertTrue(re.findall("^%s$" % re.escape(opcls.OP_ID),
80 83a2da0f Michael Hanselmann
                                 hooksdoc, re.M),
81 83a2da0f Michael Hanselmann
                      msg=("Missing hook documentation for %s" %
82 83a2da0f Michael Hanselmann
                           (opcls.OP_ID)))
83 83a2da0f Michael Hanselmann
84 3f991867 Michael Hanselmann
    pattern = r"^:directory:\s*%s\s*$" % re.escape(lucls.HPATH)
85 3f991867 Michael Hanselmann
86 3f991867 Michael Hanselmann
    self.assert_(re.findall(pattern, hooksdoc, re.M),
87 3f991867 Michael Hanselmann
                 msg=("Missing documentation for hook %s/%s" %
88 3f991867 Michael Hanselmann
                      (lucls.HTYPE, lucls.HPATH)))
89 3f991867 Michael Hanselmann
90 b58a4d16 Michael Hanselmann
  def _CheckRapiResource(self, uri, fixup, handler):
91 b58a4d16 Michael Hanselmann
    docline = "%s resource." % uri
92 b58a4d16 Michael Hanselmann
    self.assertEqual(handler.__doc__.splitlines()[0].strip(), docline,
93 b58a4d16 Michael Hanselmann
                     msg=("First line of %r's docstring is not %r" %
94 b58a4d16 Michael Hanselmann
                          (handler, docline)))
95 b58a4d16 Michael Hanselmann
96 3af47e13 Michael Hanselmann
    # Apply fixes before testing
97 3af47e13 Michael Hanselmann
    for (rx, value) in fixup.items():
98 3af47e13 Michael Hanselmann
      uri = rx.sub(value, uri)
99 3af47e13 Michael Hanselmann
100 3af47e13 Michael Hanselmann
    self.assertTrue(VALID_URI_RE.match(uri), msg="Invalid URI %r" % uri)
101 3f991867 Michael Hanselmann
102 bf968b7f Michael Hanselmann
  def testRapiDocs(self):
103 bf968b7f Michael Hanselmann
    """Check whether all RAPI resources are documented.
104 bf968b7f Michael Hanselmann

105 bf968b7f Michael Hanselmann
    """
106 bf968b7f Michael Hanselmann
    rapidoc = self._ReadDocFile("rapi.rst")
107 bf968b7f Michael Hanselmann
108 3af47e13 Michael Hanselmann
    node_name = re.escape("[node_name]")
109 3af47e13 Michael Hanselmann
    instance_name = re.escape("[instance_name]")
110 3af47e13 Michael Hanselmann
    group_name = re.escape("[group_name]")
111 3af47e13 Michael Hanselmann
    job_id = re.escape("[job_id]")
112 3af47e13 Michael Hanselmann
    disk_index = re.escape("[disk_index]")
113 1c7fd467 Michael Hanselmann
    query_res = re.escape("[resource]")
114 3af47e13 Michael Hanselmann
115 3af47e13 Michael Hanselmann
    resources = connector.GetHandlers(node_name, instance_name, group_name,
116 1c7fd467 Michael Hanselmann
                                      job_id, disk_index, query_res)
117 3af47e13 Michael Hanselmann
118 d50a2223 Michael Hanselmann
    handler_dups = utils.FindDuplicates(resources.values())
119 d50a2223 Michael Hanselmann
    self.assertFalse(handler_dups,
120 d50a2223 Michael Hanselmann
                     msg=("Resource handlers used more than once: %r" %
121 d50a2223 Michael Hanselmann
                          handler_dups))
122 d50a2223 Michael Hanselmann
123 3af47e13 Michael Hanselmann
    uri_check_fixup = {
124 3af47e13 Michael Hanselmann
      re.compile(node_name): "node1examplecom",
125 3af47e13 Michael Hanselmann
      re.compile(instance_name): "inst1examplecom",
126 3af47e13 Michael Hanselmann
      re.compile(group_name): "group4440",
127 3af47e13 Michael Hanselmann
      re.compile(job_id): "9409",
128 3af47e13 Michael Hanselmann
      re.compile(disk_index): "123",
129 1c7fd467 Michael Hanselmann
      re.compile(query_res): "lock",
130 3af47e13 Michael Hanselmann
      }
131 bf968b7f Michael Hanselmann
132 3af47e13 Michael Hanselmann
    assert compat.all(VALID_URI_RE.match(value)
133 3af47e13 Michael Hanselmann
                      for value in uri_check_fixup.values()), \
134 3af47e13 Michael Hanselmann
           "Fixup values must be valid URIs, too"
135 bf968b7f Michael Hanselmann
136 bf968b7f Michael Hanselmann
    titles = []
137 bf968b7f Michael Hanselmann
138 bf968b7f Michael Hanselmann
    prevline = None
139 bf968b7f Michael Hanselmann
    for line in rapidoc.splitlines():
140 bf968b7f Michael Hanselmann
      if re.match(r"^\++$", line):
141 bf968b7f Michael Hanselmann
        titles.append(prevline)
142 bf968b7f Michael Hanselmann
143 bf968b7f Michael Hanselmann
      prevline = line
144 bf968b7f Michael Hanselmann
145 2c0be3d0 Michael Hanselmann
    prefix_exception = frozenset(["/", "/version", "/2"])
146 2c0be3d0 Michael Hanselmann
147 bf968b7f Michael Hanselmann
    undocumented = []
148 d50a2223 Michael Hanselmann
    used_uris = []
149 bf968b7f Michael Hanselmann
150 bf968b7f Michael Hanselmann
    for key, handler in resources.iteritems():
151 bf968b7f Michael Hanselmann
      # Regex objects
152 bf968b7f Michael Hanselmann
      if hasattr(key, "match"):
153 2c0be3d0 Michael Hanselmann
        self.assert_(key.pattern.startswith("^/2/"),
154 2c0be3d0 Michael Hanselmann
                     msg="Pattern %r does not start with '^/2/'" % key.pattern)
155 3af47e13 Michael Hanselmann
        self.assertEqual(key.pattern[-1], "$")
156 2c0be3d0 Michael Hanselmann
157 bf968b7f Michael Hanselmann
        found = False
158 bf968b7f Michael Hanselmann
        for title in titles:
159 3af47e13 Michael Hanselmann
          if title.startswith("``") and title.endswith("``"):
160 3af47e13 Michael Hanselmann
            uri = title[2:-2]
161 3af47e13 Michael Hanselmann
            if key.match(uri):
162 b58a4d16 Michael Hanselmann
              self._CheckRapiResource(uri, uri_check_fixup, handler)
163 d50a2223 Michael Hanselmann
              used_uris.append(uri)
164 3af47e13 Michael Hanselmann
              found = True
165 3af47e13 Michael Hanselmann
              break
166 bf968b7f Michael Hanselmann
167 bf968b7f Michael Hanselmann
        if not found:
168 bf968b7f Michael Hanselmann
          # TODO: Find better way of identifying resource
169 2c0be3d0 Michael Hanselmann
          undocumented.append(key.pattern)
170 2c0be3d0 Michael Hanselmann
171 2c0be3d0 Michael Hanselmann
      else:
172 2c0be3d0 Michael Hanselmann
        self.assert_(key.startswith("/2/") or key in prefix_exception,
173 2c0be3d0 Michael Hanselmann
                     msg="Path %r does not start with '/2/'" % key)
174 bf968b7f Michael Hanselmann
175 3af47e13 Michael Hanselmann
        if ("``%s``" % key) in titles:
176 b58a4d16 Michael Hanselmann
          self._CheckRapiResource(key, {}, handler)
177 d50a2223 Michael Hanselmann
          used_uris.append(key)
178 3af47e13 Michael Hanselmann
        else:
179 2c0be3d0 Michael Hanselmann
          undocumented.append(key)
180 bf968b7f Michael Hanselmann
181 bf968b7f Michael Hanselmann
    self.failIf(undocumented,
182 bf968b7f Michael Hanselmann
                msg=("Missing RAPI resource documentation for %s" %
183 ab3e6da8 Iustin Pop
                     utils.CommaJoin(undocumented)))
184 bf968b7f Michael Hanselmann
185 d50a2223 Michael Hanselmann
    uri_dups = utils.FindDuplicates(used_uris)
186 d50a2223 Michael Hanselmann
    self.failIf(uri_dups,
187 d50a2223 Michael Hanselmann
                msg=("URIs matched by more than one resource: %s" %
188 d50a2223 Michael Hanselmann
                     utils.CommaJoin(uri_dups)))
189 d50a2223 Michael Hanselmann
190 bf968b7f Michael Hanselmann
191 36bf7973 Michael Hanselmann
class TestManpages(unittest.TestCase):
192 36bf7973 Michael Hanselmann
  """Manpage tests"""
193 36bf7973 Michael Hanselmann
194 36bf7973 Michael Hanselmann
  @staticmethod
195 36bf7973 Michael Hanselmann
  def _ReadManFile(name):
196 6be8e2bf Iustin Pop
    return utils.ReadFile("%s/man/%s.rst" %
197 36bf7973 Michael Hanselmann
                          (testutils.GetSourceDir(), name))
198 36bf7973 Michael Hanselmann
199 36bf7973 Michael Hanselmann
  @staticmethod
200 36bf7973 Michael Hanselmann
  def _LoadScript(name):
201 e948770c Michael Hanselmann
    return build.LoadModule("scripts/%s" % name)
202 36bf7973 Michael Hanselmann
203 36bf7973 Michael Hanselmann
  def test(self):
204 36bf7973 Michael Hanselmann
    for script in _autoconf.GNT_SCRIPTS:
205 36bf7973 Michael Hanselmann
      self._CheckManpage(script,
206 36bf7973 Michael Hanselmann
                         self._ReadManFile(script),
207 36bf7973 Michael Hanselmann
                         self._LoadScript(script).commands.keys())
208 36bf7973 Michael Hanselmann
209 36bf7973 Michael Hanselmann
  def _CheckManpage(self, script, mantext, commands):
210 36bf7973 Michael Hanselmann
    missing = []
211 36bf7973 Michael Hanselmann
212 36bf7973 Michael Hanselmann
    for cmd in commands:
213 6be8e2bf Iustin Pop
      pattern = r"^(\| )?\*\*%s\*\*" % re.escape(cmd)
214 6be8e2bf Iustin Pop
      if not re.findall(pattern, mantext, re.DOTALL | re.MULTILINE):
215 36bf7973 Michael Hanselmann
        missing.append(cmd)
216 36bf7973 Michael Hanselmann
217 36bf7973 Michael Hanselmann
    self.failIf(missing,
218 36bf7973 Michael Hanselmann
                msg=("Manpage for '%s' missing documentation for %s" %
219 ab3e6da8 Iustin Pop
                     (script, utils.CommaJoin(missing))))
220 36bf7973 Michael Hanselmann
221 36bf7973 Michael Hanselmann
222 3f991867 Michael Hanselmann
if __name__ == "__main__":
223 25231ec5 Michael Hanselmann
  testutils.GanetiTestProgram()