Statistics
| Branch: | Tag: | Revision:

root / test / docs_unittest.py @ c1912a48

History | View | Annotate | Download (10.2 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 b8028dcf Michael Hanselmann
RAPI_OPCODE_EXCLUDE = compat.UniqueFrozenset([
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 e4d745a7 Michael Hanselmann
  # Very sensitive in nature
70 e4d745a7 Michael Hanselmann
  opcodes.OpRestrictedCommand,
71 e4d745a7 Michael Hanselmann
72 8497c267 Michael Hanselmann
  # Helper opcodes (e.g. submitted by LUs)
73 8497c267 Michael Hanselmann
  opcodes.OpClusterVerifyConfig,
74 8497c267 Michael Hanselmann
  opcodes.OpClusterVerifyGroup,
75 8497c267 Michael Hanselmann
  opcodes.OpGroupEvacuate,
76 8497c267 Michael Hanselmann
  opcodes.OpGroupVerifyDisks,
77 8497c267 Michael Hanselmann
78 8497c267 Michael Hanselmann
  # Test opcodes
79 8497c267 Michael Hanselmann
  opcodes.OpTestAllocator,
80 8497c267 Michael Hanselmann
  opcodes.OpTestDelay,
81 8497c267 Michael Hanselmann
  opcodes.OpTestDummy,
82 8497c267 Michael Hanselmann
  opcodes.OpTestJqueue,
83 8497c267 Michael Hanselmann
  ])
84 8497c267 Michael Hanselmann
85 3af47e13 Michael Hanselmann
86 bf317058 Michael Hanselmann
def _ReadDocFile(filename):
87 bf317058 Michael Hanselmann
  return utils.ReadFile("%s/doc/%s" %
88 bf317058 Michael Hanselmann
                        (testutils.GetSourceDir(), filename))
89 bf317058 Michael Hanselmann
90 3f991867 Michael Hanselmann
91 1315b792 Michael Hanselmann
class TestHooksDocs(unittest.TestCase):
92 b8028dcf Michael Hanselmann
  HOOK_PATH_OK = compat.UniqueFrozenset([
93 2fd213a6 René Nussbaumer
    "master-ip-turnup",
94 2fd213a6 René Nussbaumer
    "master-ip-turndown",
95 2fd213a6 René Nussbaumer
    ])
96 2fd213a6 René Nussbaumer
97 1315b792 Michael Hanselmann
  def test(self):
98 3f991867 Michael Hanselmann
    """Check whether all hooks are documented.
99 3f991867 Michael Hanselmann

100 3f991867 Michael Hanselmann
    """
101 bf317058 Michael Hanselmann
    hooksdoc = _ReadDocFile("hooks.rst")
102 3f991867 Michael Hanselmann
103 83a2da0f Michael Hanselmann
    # Reverse mapping from LU to opcode
104 83a2da0f Michael Hanselmann
    lu2opcode = dict((lu, op)
105 83a2da0f Michael Hanselmann
                     for (op, lu) in mcpu.Processor.DISPATCH_TABLE.items())
106 83a2da0f Michael Hanselmann
    assert len(lu2opcode) == len(mcpu.Processor.DISPATCH_TABLE), \
107 83a2da0f Michael Hanselmann
      "Found duplicate entries"
108 83a2da0f Michael Hanselmann
109 2fd213a6 René Nussbaumer
    hooks_paths = frozenset(re.findall("^:directory:\s*(.+)\s*$", hooksdoc,
110 2fd213a6 René Nussbaumer
                                       re.M))
111 2fd213a6 René Nussbaumer
    self.assertTrue(self.HOOK_PATH_OK.issubset(hooks_paths),
112 2fd213a6 René Nussbaumer
                    msg="Whitelisted path not found in documentation")
113 83a2da0f Michael Hanselmann
114 2fd213a6 René Nussbaumer
    raw_hooks_ops = re.findall("^OP_(?!CODE$).+$", hooksdoc, re.M)
115 2fd213a6 René Nussbaumer
    hooks_ops = set()
116 2fd213a6 René Nussbaumer
    duplicate_ops = set()
117 2fd213a6 René Nussbaumer
    for op in raw_hooks_ops:
118 2fd213a6 René Nussbaumer
      if op in hooks_ops:
119 2fd213a6 René Nussbaumer
        duplicate_ops.add(op)
120 2fd213a6 René Nussbaumer
      else:
121 2fd213a6 René Nussbaumer
        hooks_ops.add(op)
122 3f991867 Michael Hanselmann
123 2fd213a6 René Nussbaumer
    self.assertFalse(duplicate_ops,
124 2fd213a6 René Nussbaumer
                     msg="Found duplicate opcode documentation: %s" %
125 2fd213a6 René Nussbaumer
                         utils.CommaJoin(duplicate_ops))
126 3f991867 Michael Hanselmann
127 2fd213a6 René Nussbaumer
    seen_paths = set()
128 2fd213a6 René Nussbaumer
    seen_ops = set()
129 3f991867 Michael Hanselmann
130 2fd213a6 René Nussbaumer
    self.assertFalse(duplicate_ops,
131 2fd213a6 René Nussbaumer
                     msg="Found duplicated hook documentation: %s" %
132 2fd213a6 René Nussbaumer
                         utils.CommaJoin(duplicate_ops))
133 83a2da0f Michael Hanselmann
134 2fd213a6 René Nussbaumer
    for name in dir(cmdlib):
135 2fd213a6 René Nussbaumer
      lucls = getattr(cmdlib, name)
136 2fd213a6 René Nussbaumer
137 2fd213a6 René Nussbaumer
      if (isinstance(lucls, type) and
138 2fd213a6 René Nussbaumer
          issubclass(lucls, cmdlib.LogicalUnit) and
139 2fd213a6 René Nussbaumer
          hasattr(lucls, "HPATH")):
140 2fd213a6 René Nussbaumer
        if lucls.HTYPE is None:
141 2fd213a6 René Nussbaumer
          continue
142 2fd213a6 René Nussbaumer
143 2fd213a6 René Nussbaumer
        opcls = lu2opcode.get(lucls, None)
144 2fd213a6 René Nussbaumer
145 2fd213a6 René Nussbaumer
        if opcls:
146 2fd213a6 René Nussbaumer
          seen_ops.add(opcls.OP_ID)
147 2fd213a6 René Nussbaumer
          self.assertTrue(opcls.OP_ID in hooks_ops,
148 2fd213a6 René Nussbaumer
                          msg="Missing hook documentation for %s" %
149 2fd213a6 René Nussbaumer
                              opcls.OP_ID)
150 2fd213a6 René Nussbaumer
        self.assertTrue(lucls.HPATH in hooks_paths,
151 2fd213a6 René Nussbaumer
                        msg="Missing documentation for hook %s/%s" %
152 2fd213a6 René Nussbaumer
                            (lucls.HTYPE, lucls.HPATH))
153 2fd213a6 René Nussbaumer
        seen_paths.add(lucls.HPATH)
154 2fd213a6 René Nussbaumer
155 2fd213a6 René Nussbaumer
    missed_ops = hooks_ops - seen_ops
156 2fd213a6 René Nussbaumer
    missed_paths = hooks_paths - seen_paths - self.HOOK_PATH_OK
157 2fd213a6 René Nussbaumer
158 2fd213a6 René Nussbaumer
    self.assertFalse(missed_ops,
159 2fd213a6 René Nussbaumer
                     msg="Op documents hook not existing anymore: %s" %
160 2fd213a6 René Nussbaumer
                         utils.CommaJoin(missed_ops))
161 2fd213a6 René Nussbaumer
162 2fd213a6 René Nussbaumer
    self.assertFalse(missed_paths,
163 2fd213a6 René Nussbaumer
                     msg="Hook path does not exist in opcode: %s" %
164 2fd213a6 René Nussbaumer
                         utils.CommaJoin(missed_paths))
165 3f991867 Michael Hanselmann
166 1315b792 Michael Hanselmann
167 1315b792 Michael Hanselmann
class TestRapiDocs(unittest.TestCase):
168 b58a4d16 Michael Hanselmann
  def _CheckRapiResource(self, uri, fixup, handler):
169 b58a4d16 Michael Hanselmann
    docline = "%s resource." % uri
170 b58a4d16 Michael Hanselmann
    self.assertEqual(handler.__doc__.splitlines()[0].strip(), docline,
171 b58a4d16 Michael Hanselmann
                     msg=("First line of %r's docstring is not %r" %
172 b58a4d16 Michael Hanselmann
                          (handler, docline)))
173 b58a4d16 Michael Hanselmann
174 3af47e13 Michael Hanselmann
    # Apply fixes before testing
175 3af47e13 Michael Hanselmann
    for (rx, value) in fixup.items():
176 3af47e13 Michael Hanselmann
      uri = rx.sub(value, uri)
177 3af47e13 Michael Hanselmann
178 3af47e13 Michael Hanselmann
    self.assertTrue(VALID_URI_RE.match(uri), msg="Invalid URI %r" % uri)
179 3f991867 Michael Hanselmann
180 1315b792 Michael Hanselmann
  def test(self):
181 bf968b7f Michael Hanselmann
    """Check whether all RAPI resources are documented.
182 bf968b7f Michael Hanselmann

183 bf968b7f Michael Hanselmann
    """
184 bf317058 Michael Hanselmann
    rapidoc = _ReadDocFile("rapi.rst")
185 bf968b7f Michael Hanselmann
186 3af47e13 Michael Hanselmann
    node_name = re.escape("[node_name]")
187 3af47e13 Michael Hanselmann
    instance_name = re.escape("[instance_name]")
188 3af47e13 Michael Hanselmann
    group_name = re.escape("[group_name]")
189 6e8091f9 Dimitris Aragiorgis
    network_name = re.escape("[network_name]")
190 3af47e13 Michael Hanselmann
    job_id = re.escape("[job_id]")
191 3af47e13 Michael Hanselmann
    disk_index = re.escape("[disk_index]")
192 1c7fd467 Michael Hanselmann
    query_res = re.escape("[resource]")
193 3af47e13 Michael Hanselmann
194 6e8091f9 Dimitris Aragiorgis
    resources = connector.GetHandlers(node_name, instance_name,
195 6e8091f9 Dimitris Aragiorgis
                                      group_name, network_name,
196 1c7fd467 Michael Hanselmann
                                      job_id, disk_index, query_res)
197 3af47e13 Michael Hanselmann
198 d50a2223 Michael Hanselmann
    handler_dups = utils.FindDuplicates(resources.values())
199 d50a2223 Michael Hanselmann
    self.assertFalse(handler_dups,
200 d50a2223 Michael Hanselmann
                     msg=("Resource handlers used more than once: %r" %
201 d50a2223 Michael Hanselmann
                          handler_dups))
202 d50a2223 Michael Hanselmann
203 3af47e13 Michael Hanselmann
    uri_check_fixup = {
204 3af47e13 Michael Hanselmann
      re.compile(node_name): "node1examplecom",
205 3af47e13 Michael Hanselmann
      re.compile(instance_name): "inst1examplecom",
206 3af47e13 Michael Hanselmann
      re.compile(group_name): "group4440",
207 6e8091f9 Dimitris Aragiorgis
      re.compile(network_name): "network5550",
208 3af47e13 Michael Hanselmann
      re.compile(job_id): "9409",
209 3af47e13 Michael Hanselmann
      re.compile(disk_index): "123",
210 1c7fd467 Michael Hanselmann
      re.compile(query_res): "lock",
211 3af47e13 Michael Hanselmann
      }
212 bf968b7f Michael Hanselmann
213 3af47e13 Michael Hanselmann
    assert compat.all(VALID_URI_RE.match(value)
214 3af47e13 Michael Hanselmann
                      for value in uri_check_fixup.values()), \
215 3af47e13 Michael Hanselmann
           "Fixup values must be valid URIs, too"
216 bf968b7f Michael Hanselmann
217 bf968b7f Michael Hanselmann
    titles = []
218 bf968b7f Michael Hanselmann
219 bf968b7f Michael Hanselmann
    prevline = None
220 bf968b7f Michael Hanselmann
    for line in rapidoc.splitlines():
221 bf968b7f Michael Hanselmann
      if re.match(r"^\++$", line):
222 bf968b7f Michael Hanselmann
        titles.append(prevline)
223 bf968b7f Michael Hanselmann
224 bf968b7f Michael Hanselmann
      prevline = line
225 bf968b7f Michael Hanselmann
226 b8028dcf Michael Hanselmann
    prefix_exception = compat.UniqueFrozenset(["/", "/version", "/2"])
227 2c0be3d0 Michael Hanselmann
228 bf968b7f Michael Hanselmann
    undocumented = []
229 d50a2223 Michael Hanselmann
    used_uris = []
230 bf968b7f Michael Hanselmann
231 bf968b7f Michael Hanselmann
    for key, handler in resources.iteritems():
232 bf968b7f Michael Hanselmann
      # Regex objects
233 bf968b7f Michael Hanselmann
      if hasattr(key, "match"):
234 2c0be3d0 Michael Hanselmann
        self.assert_(key.pattern.startswith("^/2/"),
235 2c0be3d0 Michael Hanselmann
                     msg="Pattern %r does not start with '^/2/'" % key.pattern)
236 3af47e13 Michael Hanselmann
        self.assertEqual(key.pattern[-1], "$")
237 2c0be3d0 Michael Hanselmann
238 bf968b7f Michael Hanselmann
        found = False
239 bf968b7f Michael Hanselmann
        for title in titles:
240 3af47e13 Michael Hanselmann
          if title.startswith("``") and title.endswith("``"):
241 3af47e13 Michael Hanselmann
            uri = title[2:-2]
242 3af47e13 Michael Hanselmann
            if key.match(uri):
243 b58a4d16 Michael Hanselmann
              self._CheckRapiResource(uri, uri_check_fixup, handler)
244 d50a2223 Michael Hanselmann
              used_uris.append(uri)
245 3af47e13 Michael Hanselmann
              found = True
246 3af47e13 Michael Hanselmann
              break
247 bf968b7f Michael Hanselmann
248 bf968b7f Michael Hanselmann
        if not found:
249 bf968b7f Michael Hanselmann
          # TODO: Find better way of identifying resource
250 2c0be3d0 Michael Hanselmann
          undocumented.append(key.pattern)
251 2c0be3d0 Michael Hanselmann
252 2c0be3d0 Michael Hanselmann
      else:
253 2c0be3d0 Michael Hanselmann
        self.assert_(key.startswith("/2/") or key in prefix_exception,
254 2c0be3d0 Michael Hanselmann
                     msg="Path %r does not start with '/2/'" % key)
255 bf968b7f Michael Hanselmann
256 3af47e13 Michael Hanselmann
        if ("``%s``" % key) in titles:
257 b58a4d16 Michael Hanselmann
          self._CheckRapiResource(key, {}, handler)
258 d50a2223 Michael Hanselmann
          used_uris.append(key)
259 3af47e13 Michael Hanselmann
        else:
260 2c0be3d0 Michael Hanselmann
          undocumented.append(key)
261 bf968b7f Michael Hanselmann
262 bf968b7f Michael Hanselmann
    self.failIf(undocumented,
263 bf968b7f Michael Hanselmann
                msg=("Missing RAPI resource documentation for %s" %
264 ab3e6da8 Iustin Pop
                     utils.CommaJoin(undocumented)))
265 bf968b7f Michael Hanselmann
266 d50a2223 Michael Hanselmann
    uri_dups = utils.FindDuplicates(used_uris)
267 d50a2223 Michael Hanselmann
    self.failIf(uri_dups,
268 d50a2223 Michael Hanselmann
                msg=("URIs matched by more than one resource: %s" %
269 d50a2223 Michael Hanselmann
                     utils.CommaJoin(uri_dups)))
270 d50a2223 Michael Hanselmann
271 8497c267 Michael Hanselmann
    self._FindRapiMissing(resources.values())
272 8497c267 Michael Hanselmann
    self._CheckTagHandlers(resources.values())
273 8497c267 Michael Hanselmann
274 8497c267 Michael Hanselmann
  def _FindRapiMissing(self, handlers):
275 8497c267 Michael Hanselmann
    used = frozenset(itertools.chain(*map(baserlib.GetResourceOpcodes,
276 8497c267 Michael Hanselmann
                                          handlers)))
277 8497c267 Michael Hanselmann
278 8497c267 Michael Hanselmann
    unexpected = used & RAPI_OPCODE_EXCLUDE
279 8497c267 Michael Hanselmann
    self.assertFalse(unexpected,
280 8497c267 Michael Hanselmann
      msg=("Found RAPI resources for excluded opcodes: %s" %
281 8497c267 Michael Hanselmann
           utils.CommaJoin(_GetOpIds(unexpected))))
282 8497c267 Michael Hanselmann
283 8497c267 Michael Hanselmann
    missing = (frozenset(opcodes.OP_MAPPING.values()) - used -
284 8497c267 Michael Hanselmann
               RAPI_OPCODE_EXCLUDE)
285 8497c267 Michael Hanselmann
    self.assertFalse(missing,
286 8497c267 Michael Hanselmann
      msg=("Missing RAPI resources for opcodes: %s" %
287 8497c267 Michael Hanselmann
           utils.CommaJoin(_GetOpIds(missing))))
288 8497c267 Michael Hanselmann
289 8497c267 Michael Hanselmann
  def _CheckTagHandlers(self, handlers):
290 8497c267 Michael Hanselmann
    tag_handlers = filter(lambda x: issubclass(x, rlib2._R_Tags), handlers)
291 8497c267 Michael Hanselmann
    self.assertEqual(frozenset(map(operator.attrgetter("TAG_LEVEL"),
292 8497c267 Michael Hanselmann
                                   tag_handlers)),
293 8497c267 Michael Hanselmann
                     constants.VALID_TAG_TYPES)
294 8497c267 Michael Hanselmann
295 8497c267 Michael Hanselmann
296 8497c267 Michael Hanselmann
def _GetOpIds(ops):
297 8497c267 Michael Hanselmann
  """Returns C{OP_ID} for all opcodes in passed sequence.
298 8497c267 Michael Hanselmann

299 8497c267 Michael Hanselmann
  """
300 8497c267 Michael Hanselmann
  return sorted(opcls.OP_ID for opcls in ops)
301 8497c267 Michael Hanselmann
302 bf968b7f Michael Hanselmann
303 36bf7973 Michael Hanselmann
class TestManpages(unittest.TestCase):
304 36bf7973 Michael Hanselmann
  """Manpage tests"""
305 36bf7973 Michael Hanselmann
306 36bf7973 Michael Hanselmann
  @staticmethod
307 36bf7973 Michael Hanselmann
  def _ReadManFile(name):
308 6be8e2bf Iustin Pop
    return utils.ReadFile("%s/man/%s.rst" %
309 36bf7973 Michael Hanselmann
                          (testutils.GetSourceDir(), name))
310 36bf7973 Michael Hanselmann
311 36bf7973 Michael Hanselmann
  @staticmethod
312 36bf7973 Michael Hanselmann
  def _LoadScript(name):
313 e948770c Michael Hanselmann
    return build.LoadModule("scripts/%s" % name)
314 36bf7973 Michael Hanselmann
315 36bf7973 Michael Hanselmann
  def test(self):
316 36bf7973 Michael Hanselmann
    for script in _autoconf.GNT_SCRIPTS:
317 36bf7973 Michael Hanselmann
      self._CheckManpage(script,
318 36bf7973 Michael Hanselmann
                         self._ReadManFile(script),
319 36bf7973 Michael Hanselmann
                         self._LoadScript(script).commands.keys())
320 36bf7973 Michael Hanselmann
321 36bf7973 Michael Hanselmann
  def _CheckManpage(self, script, mantext, commands):
322 36bf7973 Michael Hanselmann
    missing = []
323 36bf7973 Michael Hanselmann
324 36bf7973 Michael Hanselmann
    for cmd in commands:
325 6be8e2bf Iustin Pop
      pattern = r"^(\| )?\*\*%s\*\*" % re.escape(cmd)
326 6be8e2bf Iustin Pop
      if not re.findall(pattern, mantext, re.DOTALL | re.MULTILINE):
327 36bf7973 Michael Hanselmann
        missing.append(cmd)
328 36bf7973 Michael Hanselmann
329 36bf7973 Michael Hanselmann
    self.failIf(missing,
330 36bf7973 Michael Hanselmann
                msg=("Manpage for '%s' missing documentation for %s" %
331 ab3e6da8 Iustin Pop
                     (script, utils.CommaJoin(missing))))
332 36bf7973 Michael Hanselmann
333 36bf7973 Michael Hanselmann
334 3f991867 Michael Hanselmann
if __name__ == "__main__":
335 25231ec5 Michael Hanselmann
  testutils.GanetiTestProgram()