Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python | |
| 2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """ Parse suite control files and make HTML documentation from included tests. | |
| 7 | |
| 8 This program will create a list of test cases found in suite files by parsing | |
| 9 through each suite control file and making a list of all of the jobs called from | |
| 10 it. Once it has a list of tests, it will parse the AutoTest control file for | |
| 11 each test and grab the doc strings. These doc strings, along with any | |
| 12 constraints in the suite control file, will be added to the original test | |
| 13 script. These new scripts will be placed in a stand alone directory. Doxygen | |
| 14 will then use these files for the sole purpose of producing HTML documentation | |
| 15 for all of the tests. Once HTML docs are created some post processing will be | |
| 16 done against the docs to change a few strings. | |
| 17 | |
| 18 If this script is executed without a --src argument, it will assume it is being | |
| 19 executed from <ChromeOS>/src/third_party/autotest/files/utils/docgen/ directory. | |
| 20 | |
| 21 Classes: | |
| 22 | |
| 23 DocCreator | |
| 24 This class is responsible for all processing. It requires the following: | |
| 25 - Absolute path of suite control files. | |
| 26 - Absolute path of where to place temporary files it constructs from the | |
| 27 control files and test scripts. | |
| 28 This class makes the following assumptions: | |
| 29 - Each master suite has a README.txt file with general instructions on | |
| 30 test preparation and usage. | |
| 31 - The control file for each test has doc strings with labels of: | |
| 32 - PURPOSE: one line description of why this test exists. | |
| 33 - CRITERIA: Pass/Failure conditions. | |
| 34 - DOC: additional test details. | |
| 35 ReadNode | |
| 36 This class parses a node from a control file into a key/value pair. In this | |
| 37 context, a node represents a syntactic construct of an abstract syntax tree. | |
| 38 The root of the tree is the module object (in this case a control file). If | |
| 39 suite=True, it will assume the node is from a suite control file. | |
| 40 | |
| 41 Doxygen should already be configured with a configuration file called: | |
| 42 doxygen.conf. This file should live in the same directory with this program. | |
| 43 If you haven't installed doxygen, you'll need to install this program before | |
| 44 this script is executed. This program will automatically update the doxygen.conf | |
| 45 file to match self.src_tests and self.html. | |
| 46 | |
| 47 TODO: (kdlucas@google.com) Update ReadNode class to use the replacement module | |
| 48 for the compiler module, as that has been deprecated. | |
| 49 """ | |
| 50 | |
| 51 __author__ = 'kdlucas@google.com (Kelly Lucas)' | |
| 52 __version__ = '0.8.0' | |
| 53 | |
| 54 import compiler | |
| 55 import fileinput | |
| 56 import glob | |
| 57 import logging | |
| 58 import optparse | |
| 59 import os | |
| 60 import re | |
| 61 import shutil | |
| 62 import subprocess | |
| 63 import sys | |
| 64 | |
| 65 | |
| 66 class DocCreator(object): | |
| 67 """Process suite control files to combine docstrings and create HTML docs. | |
| 68 | |
| 69 The DocCreator class is designed to parse AutoTest suite control files to | |
| 70 find all of the tests referenced, and build HTML documentation based on the | |
| 71 docstrings in those files. It will cross reference the test control file | |
| 72 and any parameters passed through the suite file, with the original test | |
| 73 case. DocCreator relies on doxygen to actually generate the HTML documents. | |
| 74 | |
| 75 The workflow is as follows: | |
| 76 - Parse the suite file(s) and generate a test list. | |
| 77 - Locate the test source, and grab the docstrings from the associated | |
| 78 AutoTest control file. | |
| 79 - Combine the docstring from the control file with any parameters passed | |
| 80 in from the suite control file, with the original test case. | |
| 81 - Write a new test file with the combined docstrings to src_tests. | |
| 82 - Create HTML documentation by running doxygen against the tests stored | |
| 83 in self.src_tests. | |
| 84 | |
| 85 Implements the following methods: | |
| 86 - SetLogger() - Gets a logger and sets the formatting. | |
| 87 - GetTests() - Parse suite control files, create a dictionary of tests. | |
| 88 - ParseControlFiles() - Runs through all tests and parses control files | |
| 89 - _CleanDir() - Remove any files in a direcory and create an empty one. | |
| 90 - _GetDoctString() - Parses docstrings and joins it with constraints. | |
| 91 - _CreateTest() - Add docstrings and constraints to existing test script | |
| 92 to form a new test script. | |
| 93 - CreateMainPage() - Create a mainpage.txt file based on contents of the | |
| 94 suite README file. | |
| 95 - _ConfigDoxygen - Updates doxygen.conf to match some attributes this | |
| 96 script was run with. | |
| 97 - RunDoxygen() - Executes the doxygen program. | |
| 98 - CleanDocs() - Changes some text in the HTML files to conform to our | |
| 99 naming conventions and style. | |
| 100 | |
| 101 Depends upon class ReadNode. | |
| 102 """ | |
| 103 def __init__(self): | |
| 104 """Parse command line arguments and set some initial variables.""" | |
| 105 | |
| 106 desc="""%prog will scan AutoTest suite control files to build a list of | |
| 107 test cases called in the suite, and build HTML documentation based on | |
| 108 the docstrings it finds in the tests, control files, and suite control | |
| 109 files. | |
| 110 """ | |
| 111 | |
| 112 self.runpath = os.path.abspath('.') | |
| 113 src = os.path.join(self.runpath, '../../../../../') | |
| 114 srcdir = os.path.abspath(src) | |
| 115 | |
| 116 parser = optparse.OptionParser(description=desc, | |
| 117 prog='CreateDocs', | |
| 118 version=__version__, | |
| 119 usage='%prog') | |
| 120 parser.add_option('--debug', | |
| 121 help='Debug level [default: %default]', | |
| 122 default='debug', | |
| 123 dest='debug') | |
| 124 parser.add_option('--docversion', | |
| 125 help='Specify a version for the documentation' | |
| 126 '[default: %default]', | |
| 127 default=None, | |
| 128 dest='docversion') | |
| 129 parser.add_option('--doxy', | |
| 130 help='doxygen configuration file [default: %default]', | |
| 131 default='doxygen.conf', | |
| 132 dest='doxyconf') | |
| 133 parser.add_option('--html', | |
| 134 help='path to store html docs [default: %default]', | |
| 135 default='html', | |
| 136 dest='html') | |
| 137 parser.add_option('--latex', | |
| 138 help='path to store latex docs [default: %default]', | |
| 139 default='latex', | |
| 140 dest='latex') | |
| 141 parser.add_option('--layout', | |
| 142 help='doxygen layout file [default: %default]', | |
| 143 default='doxygenLayout.xml', | |
| 144 dest='layout') | |
| 145 parser.add_option('--log', | |
| 146 help='Logfile for program output [default: %default]', | |
| 147 default='docCreator.log', | |
| 148 dest='logfile') | |
| 149 parser.add_option('--readme', | |
| 150 help='filename of suite documentation' | |
| 151 '[default: %default]', | |
| 152 default='README.txt', | |
| 153 dest='readme') | |
| 154 parser.add_option('--src', | |
| 155 help='path to chromiumos source root' | |
| 156 ' [default: %default]', | |
| 157 default=srcdir, | |
| 158 dest='src_chromeos') | |
| 159 #TODO(kdlucas): add an all option that will parse all suites. | |
| 160 parser.add_option('--suite', | |
| 161 help='Directory name of suite [default: %default]', | |
| 162 type='choice', | |
| 163 default='suite_HWQual', | |
| 164 choices = [ | |
| 165 'suite_Factory', | |
| 166 'suite_HWConfig', | |
| 167 'suite_HWQual', | |
| 168 ], | |
| 169 dest='suite') | |
| 170 parser.add_option('--tests', | |
| 171 help='Absolute path of temporary test files' | |
| 172 ' [default: %default]', | |
| 173 default='testsource', | |
| 174 dest='src_tests') | |
| 175 | |
| 176 self.options, self.args = parser.parse_args() | |
| 177 | |
| 178 | |
| 179 # Make parameters a little shorter by making the following assignments. | |
| 180 self.debug = self.options.debug | |
| 181 self.docversion = self.options.docversion | |
| 182 self.doxyconf = self.options.doxyconf | |
| 183 self.html = self.options.html | |
| 184 self.latex = self.options.latex | |
| 185 self.layout = self.options.layout | |
| 186 self.logfile = self.options.logfile | |
| 187 self.readme = self.options.readme | |
| 188 self.src_tests = self.options.src_tests | |
| 189 self.src = self.options.src_chromeos | |
| 190 self.suite = self.options.suite | |
| 191 | |
| 192 autotest_path = 'third_party/autotest/files' | |
| 193 sitetests = 'client/site_tests' | |
| 194 tests = 'client/tests' | |
| 195 self.testcase = {} | |
| 196 | |
| 197 self.site_dir = os.path.join(self.src, autotest_path, sitetests) | |
| 198 self.test_dir = os.path.join(self.src, autotest_path, tests) | |
| 199 self.suite_dir = os.path.join(self.site_dir, self.suite) | |
| 200 | |
| 201 self.logger = self.SetLogger('docCreator') | |
| 202 self.logger.debug('Executing with debug level: %s', self.debug) | |
| 203 self.logger.debug('Writing to logfile: %s', self.logfile) | |
| 204 self.logger.debug('New test directory: %s', self.src_tests) | |
| 205 self.logger.debug('Chrome OS src directory: %s', self.src) | |
| 206 self.logger.debug('Test suite: %s', self.suite) | |
| 207 | |
| 208 self.suitename = {'suite_Factory': 'Factory Testing', | |
| 209 'suite_HWConfig': 'Hardware Configuration', | |
| 210 'suite_HWQual': 'Hardware Qualification', | |
| 211 } | |
| 212 | |
| 213 def SetLogger(self, namespace): | |
| 214 """Create a logger with some good formatting options. | |
| 215 | |
| 216 Args: | |
| 217 namespace: string, name associated with this logger. | |
| 218 Returns: | |
| 219 Logger object. | |
| 220 This method assumes self.logfile and self.debug are already set. | |
| 221 This logger will write to stdout as well as a log file. | |
| 222 """ | |
| 223 | |
| 224 loglevel = {'debug': logging.DEBUG, | |
| 225 'info': logging.INFO, | |
| 226 'warning': logging.WARNING, | |
| 227 'error': logging.ERROR, | |
| 228 'critical': logging.CRITICAL, | |
| 229 } | |
| 230 | |
| 231 logger = logging.getLogger(namespace) | |
| 232 c = logging.StreamHandler() | |
| 233 h = logging.FileHandler(os.path.join(self.runpath, self.logfile)) | |
| 234 hf = logging.Formatter( | |
| 235 '%(asctime)s %(process)d %(levelname)s: %(message)s') | |
| 236 cf = logging.Formatter('%(levelname)s: %(message)s') | |
| 237 logger.addHandler(h) | |
| 238 logger.addHandler(c) | |
| 239 h.setFormatter(hf) | |
| 240 c.setFormatter(cf) | |
| 241 | |
| 242 if self.debug in loglevel: | |
| 243 logger.setLevel(loglevel.get(self.debug, logging.INFO)) | |
|
petkov
2010/05/26 19:35:17
Remove if / else -- you don't need them any more.
| |
| 244 else: | |
| 245 logger.setLevel(logging.INFO) | |
| 246 | |
| 247 return logger | |
| 248 | |
| 249 | |
| 250 def GetTests(self): | |
| 251 """Create dictionary of tests based on suite control file contents.""" | |
| 252 | |
| 253 suite_search = os.path.join(self.suite_dir, 'control.*') | |
| 254 for suitefile in glob.glob(suite_search): | |
| 255 self.logger.debug('Scanning %s for tests', suitefile) | |
| 256 try: | |
| 257 suite = compiler.parseFile(suitefile) | |
| 258 except SyntaxError, e: | |
| 259 self.logger.error('Error parsing: %s\n%s', (suitefile, e)) | |
| 260 raise SystemExit | |
| 261 | |
| 262 # Walk through each node found in the control file, which in our | |
| 263 # case will be a call to a test. compiler.walk() will walk through | |
| 264 # each component node, and call the appropriate function in class | |
| 265 # ReadNode. The returned key should be a string, and the name of a | |
| 266 # test. visitor.value should be any extra arguments found in the | |
| 267 # suite file that are used with that test case. | |
| 268 for n in suite.node.nodes: | |
| 269 visitor = ReadNode(suite=True) | |
| 270 compiler.walk(n, visitor) | |
| 271 if len(visitor.key) > 1: | |
| 272 self.logger.debug('Found test %s', visitor.key) | |
| 273 filtered_input = '' | |
| 274 # Lines in value should start with ' -' for bullet item. | |
| 275 if visitor.value: | |
| 276 lines = visitor.value.split('\n') | |
| 277 for line in lines: | |
| 278 if line.startswith(' -'): | |
| 279 filtered_input += line + '\n' | |
| 280 # A test could be called multiple times, so see if the key | |
| 281 # already exists, and if so append the new value. | |
| 282 if visitor.key in self.testcase: | |
| 283 s = self.testcase[visitor.key] + filtered_input | |
| 284 self.testcase[visitor.key] = s | |
| 285 else: | |
| 286 self.testcase[visitor.key] = filtered_input | |
| 287 | |
| 288 | |
| 289 def _CleanDir(self, directory): | |
| 290 """Ensure the directory is available and empty. | |
| 291 | |
| 292 Args: | |
| 293 directory: string, path of directory | |
| 294 """ | |
| 295 | |
| 296 if os.path.isdir(directory): | |
| 297 try: | |
| 298 shutil.rmtree(directory) | |
| 299 except IOError, err: | |
| 300 self.logger.error('Error cleaning %s\n%s', (directory, err)) | |
| 301 try: | |
| 302 os.makedirs(directory) | |
| 303 except IOError, err: | |
| 304 self.logger.error('Error creating %s\n%s', (directory, err)) | |
| 305 self.logger.error('Check your permissions of %s', directory) | |
| 306 raise SystemExit | |
| 307 | |
| 308 | |
| 309 def ParseControlFiles(self): | |
| 310 """Get docstrings from control files and add them to new test scripts. | |
| 311 | |
| 312 This method will cycle through all of the tests and attempt to find | |
| 313 their control file. If found, it will parse the docstring from the | |
| 314 control file, add this to any parameters found in the suite file, and | |
| 315 add this combined docstring to the original test. These new tests will | |
| 316 be written in the self.src_tests directory. | |
| 317 """ | |
| 318 # Clean some target directories. | |
| 319 for d in [self.src_tests, self.html]: | |
| 320 self._CleanDir(d) | |
| 321 | |
| 322 for test in self.testcase: | |
| 323 testdir = os.path.join(self.site_dir, test) | |
| 324 if not os.path.isdir(testdir): | |
| 325 testdir = os.path.join(self.test_dir, test) | |
| 326 | |
| 327 if os.path.isdir(testdir): | |
| 328 control_file = os.path.join(testdir, 'control') | |
| 329 test_file = os.path.join(testdir, test + '.py') | |
| 330 docstring = self._GetDocString(control_file, test) | |
| 331 self._CreateTest(test_file, docstring, test) | |
| 332 else: | |
| 333 self.logger.warning('Cannot find test: %s', test) | |
| 334 | |
| 335 def _GetDocString(self, control_file, test): | |
| 336 """Get the docstrings from control file and join to suite file params. | |
| 337 | |
| 338 Args: | |
| 339 control_file: string, absolute path to test control file. | |
| 340 test: string, name of test. | |
| 341 Returns: | |
| 342 string: combined docstring with needed markup language for doxygen. | |
| 343 """ | |
| 344 | |
| 345 # Doxygen needs the @package marker. | |
| 346 package_doc = '## @package ' | |
| 347 # To allow doxygen to use special commands, we must use # for comments. | |
| 348 comment = '# ' | |
| 349 endlist = ' .\n' | |
| 350 control_dict = {} | |
| 351 output = [] | |
| 352 temp = [] | |
| 353 tempstring = '' | |
| 354 docstring = '' | |
| 355 keys = ['\\brief\n', '<H3>Pass/Fail Criteria:</H3>\n', | |
| 356 '<H3>Author</H3>\n', '<H3>Test Duration</H3>\n', | |
| 357 '<H3>Category</H3>\n', '<H3>Test Type</H3>\n', | |
| 358 '<H3>Test Class</H3>\n', '<H3>Notest</H3>\n', | |
| 359 ] | |
| 360 | |
| 361 try: | |
| 362 control = compiler.parseFile(control_file) | |
| 363 except SyntaxError, e: | |
| 364 self.logger.error('Error parsing: %s\n%s', (control_file, e)) | |
| 365 return None | |
| 366 | |
| 367 for n in control.node.nodes: | |
| 368 visitor = ReadNode() | |
| 369 compiler.walk(n, visitor) | |
| 370 control_dict[visitor.key] = visitor.value | |
| 371 | |
| 372 for k in keys: | |
| 373 if k in control_dict: | |
| 374 if len(control_dict[k]) > 1: | |
| 375 if k != test: | |
| 376 temp.append(k) | |
| 377 temp.append(control_dict[k]) | |
| 378 if control_dict[k]: | |
| 379 temp.append(endlist) | |
| 380 # Add constraints and extra args after the Criteria section. | |
| 381 if 'Criteria:' in k: | |
| 382 if self.testcase[test]: | |
| 383 temp.append('<H3>Arguments:</H3>\n') | |
| 384 temp.append(self.testcase[test]) | |
| 385 # '.' character at the same level as the '-' tells | |
| 386 # doxygen this is the end of the list. | |
| 387 temp.append(endlist) | |
| 388 | |
| 389 output.append(package_doc + test + '\n') | |
| 390 tempstring = "".join(temp) | |
| 391 lines = tempstring.split('\n') | |
| 392 for line in lines: | |
| 393 # Doxygen requires a '#' character to add special doxygen commands. | |
| 394 comment_line = comment + line + '\n' | |
| 395 output.append(comment_line) | |
| 396 | |
| 397 docstring = "".join(output) | |
| 398 | |
| 399 return docstring | |
| 400 | |
| 401 | |
| 402 def _CreateTest(self, test_file, docstring, test): | |
| 403 """Create a new test with the combined docstrings from multiple sources. | |
| 404 | |
| 405 Args: | |
| 406 test_file: string, file name of new test to write. | |
| 407 docstring: string, the docstring to add to the existing test. | |
| 408 test: string, name of the test. | |
| 409 | |
| 410 This method is used to create a temporary copy of a new test, that will | |
| 411 be a combination of the original test plus the docstrings from the | |
| 412 control file, and any constraints from the suite control file. | |
| 413 """ | |
| 414 | |
| 415 class_def = 'class ' + test | |
| 416 pathname = os.path.join(self.src_tests, test + '.py') | |
| 417 | |
| 418 # Open the test and write out new test with added docstrings | |
| 419 try: | |
| 420 f = open(test_file, 'r') | |
| 421 except IOError, err: | |
| 422 self.logger.error('Error while reading %s\n%s', (test_file, err)) | |
| 423 return | |
| 424 lines = f.readlines() | |
| 425 f.close() | |
| 426 | |
| 427 try: | |
| 428 f = open(pathname, 'w') | |
| 429 except IOError, err: | |
| 430 self.logger.error('Error creating %s\n%s', (pathname, err)) | |
| 431 return | |
| 432 | |
| 433 for line in lines: | |
| 434 if class_def in line: | |
| 435 f.write(docstring) | |
| 436 f.write('\n') | |
| 437 f.write(line) | |
| 438 f.close() | |
| 439 | |
| 440 def CreateMainPage(self): | |
| 441 """Create a main page to provide content for index.html. | |
| 442 | |
| 443 This method assumes a file named README.txt is located in your suite | |
| 444 directory with general instructions on setting up and using the suite. | |
| 445 If your README file is in another file, ensure you pass a --readme | |
| 446 option with the correct filename. To produce a better looking | |
| 447 landing page, use the '-' character for list items. This method assumes | |
| 448 os commands start with '$'. | |
| 449 """ | |
| 450 | |
| 451 # Define some strings that Doxygen uses for specific formatting. | |
| 452 cstart = '/**' | |
| 453 cend = '**/' | |
| 454 mp = '@mainpage' | |
| 455 section_begin = '@section ' | |
| 456 vstart = '@verbatim ' | |
| 457 vend = ' @endverbatim\n' | |
| 458 | |
| 459 # Define some characters we expect to delineate sections in the README. | |
| 460 sec_char = '==========' | |
| 461 command_prompt = '$ ' | |
| 462 command_cont = '\\' | |
| 463 | |
| 464 command = False | |
| 465 comment = False | |
| 466 section = False | |
| 467 sec_ctr = 0 | |
| 468 | |
| 469 readme_file = os.path.join(self.suite_dir, self.readme) | |
| 470 mainpage_file = os.path.join(self.src_tests, 'mainpage.txt') | |
| 471 | |
| 472 try: | |
| 473 f = open(readme_file, 'r') | |
| 474 except IOError, err: | |
| 475 self.logger.error('Error opening %s\n%s', (readme_file, err)) | |
| 476 return | |
| 477 try: | |
| 478 fw = open(mainpage_file, 'w') | |
| 479 except IOError, err: | |
| 480 self.logger.error('Error opening %s\n%s', (mainpage_file, err)) | |
| 481 return | |
| 482 | |
| 483 lines = f.readlines() | |
| 484 f.close() | |
| 485 | |
| 486 fw.write(cstart) | |
| 487 fw.write('\n') | |
| 488 fw.write(mp) | |
| 489 fw.write('\n') | |
| 490 | |
| 491 for line in lines: | |
| 492 if sec_char in line: | |
| 493 comment = True | |
| 494 section = not section | |
| 495 elif section: | |
| 496 sec_ctr += 1 | |
| 497 section_name = ' section%d ' % sec_ctr | |
| 498 fw.write(section_begin + section_name + line) | |
| 499 else: | |
| 500 # comment is used to denote when we should start recording text | |
| 501 # from the README file. Some of the initial text is not needed. | |
| 502 if comment: | |
| 503 if command_prompt in line: | |
| 504 line = line.rstrip() | |
| 505 if line[-1] == command_cont: | |
| 506 fw.write(vstart + line[:-1]) | |
| 507 command = True | |
| 508 else: | |
| 509 fw.write(vstart + line + vend) | |
| 510 elif command: | |
| 511 line = line.strip() | |
| 512 if line[-1] == command_cont: | |
| 513 fw.write(line) | |
| 514 else: | |
| 515 fw.write(line + vend) | |
| 516 command = False | |
| 517 else: | |
| 518 fw.write(line) | |
| 519 | |
| 520 fw.write('\n') | |
| 521 fw.write(cend) | |
| 522 fw.close() | |
| 523 | |
| 524 def _ConfigDoxygen(self): | |
| 525 """Set Doxygen configuration to match our options.""" | |
| 526 | |
| 527 doxy_config = { | |
| 528 'ALPHABETICAL_INDEX': 'YES', | |
| 529 'EXTRACT_ALL': 'YES', | |
| 530 'EXTRACT_LOCAL_METHODS': 'YES', | |
| 531 'EXTRACT_PRIVATE': 'YES', | |
| 532 'EXTRACT_STATIC': 'YES', | |
| 533 'FILE_PATTERNS': '*.py *.txt', | |
| 534 'FULL_PATH_NAMES ': 'YES', | |
| 535 'GENERATE_TREEVIEW': 'YES', | |
| 536 'HTML_DYNAMIC_SECTIONS': 'YES', | |
| 537 'HTML_FOOTER': 'footer.html', | |
| 538 'HTML_HEADER': 'header.html', | |
| 539 'HTML_OUTPUT ': self.html, | |
| 540 'INLINE_SOURCES': 'YES', | |
| 541 'INPUT ': self.src_tests, | |
| 542 'JAVADOC_AUTOBRIEF': 'YES', | |
| 543 'LATEX_OUTPUT ': self.latex, | |
| 544 'LAYOUT_FILE ': self.layout, | |
| 545 'OPTIMIZE_OUTPUT_JAVA': 'YES', | |
| 546 'PROJECT_NAME ': self.suitename[self.suite], | |
| 547 'PROJECT_NUMBER': self.docversion, | |
| 548 'SOURCE_BROWSER': 'YES', | |
| 549 'STRIP_CODE_COMMENTS': 'NO', | |
| 550 'TAB_SIZE': '4', | |
| 551 'USE_INLINE_TREES': 'YES', | |
| 552 } | |
| 553 | |
| 554 doxy_layout = { | |
| 555 'tab type="mainpage"': 'title="%s"' % | |
| 556 self.suitename[self.suite], | |
| 557 'tab type="namespaces"': 'title="Tests"', | |
| 558 'tab type="namespacemembers"': 'title="Test Functions"', | |
| 559 } | |
| 560 | |
| 561 for line in fileinput.input(self.doxyconf, inplace=1): | |
| 562 for k in doxy_config: | |
| 563 if line.startswith(k): | |
| 564 line = '%s = %s\n' % (k, doxy_config[k]) | |
| 565 print line, | |
| 566 | |
| 567 for line in fileinput.input('header.html', inplace=1): | |
| 568 if line.startswith('<H2>'): | |
| 569 line = '<H2>%s</H2>\n' % self.suitename[self.suite] | |
| 570 print line, | |
| 571 | |
| 572 for line in fileinput.input(self.layout, inplace=1): | |
| 573 for k in doxy_layout: | |
| 574 if line.find(k) != -1: | |
| 575 line = line.replace('title=""', doxy_layout[k]) | |
| 576 print line, | |
| 577 | |
| 578 | |
| 579 def RunDoxygen(self, doxyargs): | |
| 580 """Execute Doxygen on the files in the self.src_tests directory. | |
| 581 | |
| 582 Args: | |
| 583 doxyargs: string, any command line args to be passed to doxygen. | |
| 584 """ | |
| 585 | |
| 586 doxycmd = 'doxygen %s' % doxyargs | |
| 587 | |
| 588 p = subprocess.Popen(doxycmd, shell=True, stdout=subprocess.PIPE, | |
| 589 stderr=subprocess.PIPE) | |
| 590 p.wait() | |
| 591 cmdoutput = p.stdout.read() | |
| 592 | |
| 593 if p.returncode: | |
| 594 self.logger.error('Error while running %s', doxycmd) | |
| 595 self.logger.error(cmdoutput) | |
| 596 else: | |
| 597 self.logger.info('%s successfully ran', doxycmd) | |
| 598 | |
| 599 | |
| 600 def CreateDocs(self): | |
| 601 """Configure and execute Doxygen to create HTML docuements.""" | |
| 602 | |
| 603 # First run doxygen with args to create default configuration files. | |
| 604 # Create layout xml file. | |
| 605 doxyargs = '-l %s' % self.layout | |
| 606 self.RunDoxygen(doxyargs) | |
| 607 | |
| 608 # Create doxygen configuration file. | |
| 609 doxyargs = '-g %s' % self.doxyconf | |
| 610 self.RunDoxygen(doxyargs) | |
| 611 | |
| 612 # Edit the configuration files to match our options. | |
| 613 self._ConfigDoxygen() | |
| 614 | |
| 615 # Run doxygen with configuration file as argument. | |
| 616 self.RunDoxygen(self.doxyconf) | |
| 617 | |
| 618 | |
| 619 def CleanDocs(self): | |
| 620 """Run some post processing on the newly created docs.""" | |
| 621 | |
| 622 logo_image = 'customLogo.gif' | |
| 623 | |
| 624 # Key = original string, value = replacement string. | |
| 625 replace = { | |
| 626 '>Package': '>Test', | |
| 627 } | |
| 628 | |
| 629 docpages = os.path.join(self.html, '*.html') | |
| 630 files = glob.glob(docpages) | |
| 631 for file in files: | |
| 632 for line in fileinput.input(file, inplace=1): | |
| 633 for k in replace: | |
| 634 if line.find(k) != -1: | |
| 635 line = line.replace(k, replace[k]) | |
| 636 print line, | |
| 637 | |
| 638 shutil.copy(logo_image, self.html) | |
| 639 | |
| 640 self.logger.info('Sanitized documentation completed.') | |
| 641 | |
| 642 | |
| 643 class ReadNode(object): | |
| 644 """Parse a compiler node object from a control file. | |
| 645 | |
| 646 Args: | |
| 647 suite: boolean, set to True if parsing nodes from a suite control file. | |
| 648 """ | |
| 649 | |
| 650 def __init__(self, suite=False): | |
| 651 self.key = '' | |
| 652 self.value = '' | |
| 653 self.testdef = False | |
| 654 self.suite = suite | |
| 655 self.bullet = ' - ' | |
| 656 | |
| 657 def visitName(self, n): | |
| 658 if n.name == 'job': | |
| 659 self.testdef = True | |
| 660 | |
| 661 def visitConst(self, n): | |
| 662 if self.testdef: | |
| 663 self.key = str(n.value) | |
| 664 self.testdef = False | |
| 665 else: | |
| 666 self.value += str(n.value) + '\n' | |
| 667 | |
| 668 def visitKeyword(self, n): | |
| 669 if n.name != 'constraints': | |
| 670 self.value += self.bullet + n.name + ': ' | |
| 671 for item in n.expr: | |
| 672 if isinstance(item, compiler.ast.Const): | |
| 673 for i in item: | |
| 674 self.value += self.bullet + str(i) + '\n' | |
| 675 self.value += ' .\n' | |
| 676 else: | |
| 677 self.value += str(item) + '\n' | |
| 678 | |
| 679 | |
| 680 def visitAssName(self, n): | |
| 681 # To remove section from appearing in the documentation, set value = ''. | |
| 682 sections = { | |
| 683 'AUTHOR': '', | |
| 684 'CRITERIA': '<H3>Pass/Fail Criteria:</H3>\n', | |
| 685 'DOC': '<H3>Notes</H3>\n', | |
| 686 'NAME': '', | |
| 687 'PURPOSE': '\\brief\n', | |
| 688 'TIME': '<H3>Test Duration</H3>\n', | |
| 689 'TEST_CATEGORY': '<H3>Category</H3>\n', | |
| 690 'TEST_CLASS': '<H3>Test Class</H3>\n', | |
| 691 'TEST_TYPE': '<H3>Test Type</H3>\n', | |
| 692 } | |
| 693 | |
| 694 if not self.suite: | |
| 695 self.key = sections.get(n.name, n.name) | |
| 696 | |
| 697 | |
| 698 def main(): | |
| 699 doc = DocCreator() | |
| 700 doc.GetTests() | |
| 701 doc.ParseControlFiles() | |
| 702 doc.CreateMainPage() | |
| 703 doc.CreateDocs() | |
| 704 doc.CleanDocs() | |
| 705 | |
| 706 | |
| 707 if __name__ == '__main__': | |
| 708 main() | |
| OLD | NEW |