Statistics
| Branch: | Tag: | Revision:

root / test / docs_unittest.py @ 6e7f0cd9

History | View | Annotate | Download (6.1 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.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()