Statistics
| Branch: | Tag: | Revision:

root / test / docs_unittest.py @ 1c7fd467

History | View | Annotate | Download (6.2 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
    query_res = re.escape("[resource]")
99

    
100
    resources = connector.GetHandlers(node_name, instance_name, group_name,
101
                                      job_id, disk_index, query_res)
102

    
103
    handler_dups = utils.FindDuplicates(resources.values())
104
    self.assertFalse(handler_dups,
105
                     msg=("Resource handlers used more than once: %r" %
106
                          handler_dups))
107

    
108
    uri_check_fixup = {
109
      re.compile(node_name): "node1examplecom",
110
      re.compile(instance_name): "inst1examplecom",
111
      re.compile(group_name): "group4440",
112
      re.compile(job_id): "9409",
113
      re.compile(disk_index): "123",
114
      re.compile(query_res): "lock",
115
      }
116

    
117
    assert compat.all(VALID_URI_RE.match(value)
118
                      for value in uri_check_fixup.values()), \
119
           "Fixup values must be valid URIs, too"
120

    
121
    titles = []
122

    
123
    prevline = None
124
    for line in rapidoc.splitlines():
125
      if re.match(r"^\++$", line):
126
        titles.append(prevline)
127

    
128
      prevline = line
129

    
130
    prefix_exception = frozenset(["/", "/version", "/2"])
131

    
132
    undocumented = []
133
    used_uris = []
134

    
135
    for key, handler in resources.iteritems():
136
      # Regex objects
137
      if hasattr(key, "match"):
138
        self.assert_(key.pattern.startswith("^/2/"),
139
                     msg="Pattern %r does not start with '^/2/'" % key.pattern)
140
        self.assertEqual(key.pattern[-1], "$")
141

    
142
        found = False
143
        for title in titles:
144
          if title.startswith("``") and title.endswith("``"):
145
            uri = title[2:-2]
146
            if key.match(uri):
147
              self._CheckRapiResource(uri, uri_check_fixup, handler)
148
              used_uris.append(uri)
149
              found = True
150
              break
151

    
152
        if not found:
153
          # TODO: Find better way of identifying resource
154
          undocumented.append(key.pattern)
155

    
156
      else:
157
        self.assert_(key.startswith("/2/") or key in prefix_exception,
158
                     msg="Path %r does not start with '/2/'" % key)
159

    
160
        if ("``%s``" % key) in titles:
161
          self._CheckRapiResource(key, {}, handler)
162
          used_uris.append(key)
163
        else:
164
          undocumented.append(key)
165

    
166
    self.failIf(undocumented,
167
                msg=("Missing RAPI resource documentation for %s" %
168
                     utils.CommaJoin(undocumented)))
169

    
170
    uri_dups = utils.FindDuplicates(used_uris)
171
    self.failIf(uri_dups,
172
                msg=("URIs matched by more than one resource: %s" %
173
                     utils.CommaJoin(uri_dups)))
174

    
175

    
176
class TestManpages(unittest.TestCase):
177
  """Manpage tests"""
178

    
179
  @staticmethod
180
  def _ReadManFile(name):
181
    return utils.ReadFile("%s/man/%s.rst" %
182
                          (testutils.GetSourceDir(), name))
183

    
184
  @staticmethod
185
  def _LoadScript(name):
186
    return build.LoadModule("scripts/%s" % name)
187

    
188
  def test(self):
189
    for script in _autoconf.GNT_SCRIPTS:
190
      self._CheckManpage(script,
191
                         self._ReadManFile(script),
192
                         self._LoadScript(script).commands.keys())
193

    
194
  def _CheckManpage(self, script, mantext, commands):
195
    missing = []
196

    
197
    for cmd in commands:
198
      pattern = r"^(\| )?\*\*%s\*\*" % re.escape(cmd)
199
      if not re.findall(pattern, mantext, re.DOTALL | re.MULTILINE):
200
        missing.append(cmd)
201

    
202
    self.failIf(missing,
203
                msg=("Manpage for '%s' missing documentation for %s" %
204
                     (script, utils.CommaJoin(missing))))
205

    
206

    
207
if __name__ == "__main__":
208
  testutils.GanetiTestProgram()