Statistics
| Branch: | Tag: | Revision:

root / test / docs_unittest.py @ 1315b792

History | View | Annotate | Download (6.7 kB)

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 import mcpu
33
from ganeti.rapi import connector
34

    
35
import testutils
36

    
37

    
38
VALID_URI_RE = re.compile(r"^[-/a-z0-9]*$")
39

    
40

    
41
def _ReadDocFile(filename):
42
  return utils.ReadFile("%s/doc/%s" %
43
                        (testutils.GetSourceDir(), filename))
44

    
45

    
46
class TestHooksDocs(unittest.TestCase):
47
  def test(self):
48
    """Check whether all hooks are documented.
49

50
    """
51
    hooksdoc = _ReadDocFile("hooks.rst")
52

    
53
    # Reverse mapping from LU to opcode
54
    lu2opcode = dict((lu, op)
55
                     for (op, lu) in mcpu.Processor.DISPATCH_TABLE.items())
56
    assert len(lu2opcode) == len(mcpu.Processor.DISPATCH_TABLE), \
57
      "Found duplicate entries"
58

    
59
    for name in dir(cmdlib):
60
      obj = getattr(cmdlib, name)
61

    
62
      if (isinstance(obj, type) and
63
          issubclass(obj, cmdlib.LogicalUnit) and
64
          hasattr(obj, "HPATH")):
65
        self._CheckHook(name, obj, hooksdoc, lu2opcode)
66

    
67
  def _CheckHook(self, name, lucls, hooksdoc, lu2opcode):
68
    opcls = lu2opcode.get(lucls, None)
69

    
70
    if lucls.HTYPE is None:
71
      return
72

    
73
    # TODO: Improve this test (e.g. find hooks documented but no longer
74
    # existing)
75

    
76
    if opcls:
77
      self.assertTrue(re.findall("^%s$" % re.escape(opcls.OP_ID),
78
                                 hooksdoc, re.M),
79
                      msg=("Missing hook documentation for %s" %
80
                           (opcls.OP_ID)))
81

    
82
    pattern = r"^:directory:\s*%s\s*$" % re.escape(lucls.HPATH)
83

    
84
    self.assert_(re.findall(pattern, hooksdoc, re.M),
85
                 msg=("Missing documentation for hook %s/%s" %
86
                      (lucls.HTYPE, lucls.HPATH)))
87

    
88

    
89
class TestRapiDocs(unittest.TestCase):
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" %
94
                          (handler, docline)))
95

    
96
    # Apply fixes before testing
97
    for (rx, value) in fixup.items():
98
      uri = rx.sub(value, uri)
99

    
100
    self.assertTrue(VALID_URI_RE.match(uri), msg="Invalid URI %r" % uri)
101

    
102
  def test(self):
103
    """Check whether all RAPI resources are documented.
104

105
    """
106
    rapidoc = _ReadDocFile("rapi.rst")
107

    
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]")
114

    
115
    resources = connector.GetHandlers(node_name, instance_name, group_name,
116
                                      job_id, disk_index, query_res)
117

    
118
    handler_dups = utils.FindDuplicates(resources.values())
119
    self.assertFalse(handler_dups,
120
                     msg=("Resource handlers used more than once: %r" %
121
                          handler_dups))
122

    
123
    uri_check_fixup = {
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",
130
      }
131

    
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"
135

    
136
    titles = []
137

    
138
    prevline = None
139
    for line in rapidoc.splitlines():
140
      if re.match(r"^\++$", line):
141
        titles.append(prevline)
142

    
143
      prevline = line
144

    
145
    prefix_exception = frozenset(["/", "/version", "/2"])
146

    
147
    undocumented = []
148
    used_uris = []
149

    
150
    for key, handler in resources.iteritems():
151
      # Regex objects
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], "$")
156

    
157
        found = False
158
        for title in titles:
159
          if title.startswith("``") and title.endswith("``"):
160
            uri = title[2:-2]
161
            if key.match(uri):
162
              self._CheckRapiResource(uri, uri_check_fixup, handler)
163
              used_uris.append(uri)
164
              found = True
165
              break
166

    
167
        if not found:
168
          # TODO: Find better way of identifying resource
169
          undocumented.append(key.pattern)
170

    
171
      else:
172
        self.assert_(key.startswith("/2/") or key in prefix_exception,
173
                     msg="Path %r does not start with '/2/'" % key)
174

    
175
        if ("``%s``" % key) in titles:
176
          self._CheckRapiResource(key, {}, handler)
177
          used_uris.append(key)
178
        else:
179
          undocumented.append(key)
180

    
181
    self.failIf(undocumented,
182
                msg=("Missing RAPI resource documentation for %s" %
183
                     utils.CommaJoin(undocumented)))
184

    
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)))
189

    
190

    
191
class TestManpages(unittest.TestCase):
192
  """Manpage tests"""
193

    
194
  @staticmethod
195
  def _ReadManFile(name):
196
    return utils.ReadFile("%s/man/%s.rst" %
197
                          (testutils.GetSourceDir(), name))
198

    
199
  @staticmethod
200
  def _LoadScript(name):
201
    return build.LoadModule("scripts/%s" % name)
202

    
203
  def test(self):
204
    for script in _autoconf.GNT_SCRIPTS:
205
      self._CheckManpage(script,
206
                         self._ReadManFile(script),
207
                         self._LoadScript(script).commands.keys())
208

    
209
  def _CheckManpage(self, script, mantext, commands):
210
    missing = []
211

    
212
    for cmd in commands:
213
      pattern = r"^(\| )?\*\*%s\*\*" % re.escape(cmd)
214
      if not re.findall(pattern, mantext, re.DOTALL | re.MULTILINE):
215
        missing.append(cmd)
216

    
217
    self.failIf(missing,
218
                msg=("Manpage for '%s' missing documentation for %s" %
219
                     (script, utils.CommaJoin(missing))))
220

    
221

    
222
if __name__ == "__main__":
223
  testutils.GanetiTestProgram()