Statistics
| Branch: | Tag: | Revision:

root / test / py / docs_unittest.py @ 595149d5

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

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

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

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