788 lines
30 KiB
Python
Executable file
788 lines
30 KiB
Python
Executable file
#!/usr/bin/python
|
|
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
""" Parse suite control files and make HTML documentation from included tests.
|
|
|
|
This program will create a list of test cases found in suite files by parsing
|
|
through each suite control file and making a list of all of the jobs called from
|
|
it. Once it has a list of tests, it will parse the AutoTest control file for
|
|
each test and grab the doc strings. These doc strings, along with any
|
|
constraints in the suite control file, will be added to the original test
|
|
script. These new scripts will be placed in a stand alone directory. Doxygen
|
|
will then use these files for the sole purpose of producing HTML documentation
|
|
for all of the tests. Once HTML docs are created some post processing will be
|
|
done against the docs to change a few strings.
|
|
|
|
If this script is executed without a --src argument, it will assume it is being
|
|
executed from <ChromeOS>/src/third_party/autotest/files/utils/docgen/ directory.
|
|
|
|
Classes:
|
|
|
|
DocCreator
|
|
This class is responsible for all processing. It requires the following:
|
|
- Absolute path of suite control files.
|
|
- Absolute path of where to place temporary files it constructs from the
|
|
control files and test scripts.
|
|
This class makes the following assumptions:
|
|
- Each master suite has a README.txt file with general instructions on
|
|
test preparation and usage.
|
|
- The control file for each test has doc strings with labels of:
|
|
- PURPOSE: one line description of why this test exists.
|
|
- CRITERIA: Pass/Failure conditions.
|
|
- DOC: additional test details.
|
|
ReadNode
|
|
This class parses a node from a control file into a key/value pair. In this
|
|
context, a node represents a syntactic construct of an abstract syntax tree.
|
|
The root of the tree is the module object (in this case a control file). If
|
|
suite=True, it will assume the node is from a suite control file.
|
|
|
|
Doxygen should already be configured with a configuration file called:
|
|
doxygen.conf. This file should live in the same directory with this program.
|
|
If you haven't installed doxygen, you'll need to install this program before
|
|
this script is executed. This program will automatically update the doxygen.conf
|
|
file to match self.src_tests and self.html.
|
|
|
|
TODO: (kdlucas@google.com) Update ReadNode class to use the replacement module
|
|
for the compiler module, as that has been deprecated.
|
|
"""
|
|
|
|
__author__ = 'kdlucas@google.com (Kelly Lucas)'
|
|
__version__ = '0.9.1'
|
|
|
|
import compiler
|
|
import fileinput
|
|
import glob
|
|
import logging
|
|
import optparse
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
import fs_find_tests
|
|
|
|
|
|
class DocCreator(object):
|
|
"""Process suite control files to combine docstrings and create HTML docs.
|
|
|
|
The DocCreator class is designed to parse AutoTest suite control files to
|
|
find all of the tests referenced, and build HTML documentation based on the
|
|
docstrings in those files. It will cross reference the test control file
|
|
and any parameters passed through the suite file, with the original test
|
|
case. DocCreator relies on doxygen to actually generate the HTML documents.
|
|
|
|
The workflow is as follows:
|
|
- Parse the suite file(s) and generate a test list.
|
|
- Locate the test source, and grab the docstrings from the associated
|
|
AutoTest control file.
|
|
- Combine the docstring from the control file with any parameters passed
|
|
in from the suite control file, with the original test case.
|
|
- Write a new test file with the combined docstrings to src_tests.
|
|
- Create HTML documentation by running doxygen against the tests stored
|
|
in self.src_tests.
|
|
|
|
Implements the following methods:
|
|
- GetTests() - Parse suite control files, create a dictionary of tests.
|
|
- ParseControlFiles() - Runs through all tests and parses control files
|
|
- _CleanDir() - Remove any files in a direcory and create an empty one.
|
|
- _GetDoctString() - Parses docstrings and joins it with constraints.
|
|
- _CreateTest() - Add docstrings and constraints to existing test script
|
|
to form a new test script.
|
|
- CreateMainPage() - Create a mainpage.txt file based on contents of the
|
|
suite README file.
|
|
- _ConfigDoxygen - Updates doxygen.conf to match some attributes this
|
|
script was run with.
|
|
- RunDoxygen() - Executes the doxygen program.
|
|
- CleanDocs() - Changes some text in the HTML files to conform to our
|
|
naming conventions and style.
|
|
|
|
Depends upon class ReadNode.
|
|
"""
|
|
def __init__(self, options, args, logger):
|
|
"""Parse command line arguments and set some initial variables."""
|
|
|
|
self.options = options
|
|
self.args = args
|
|
self.logger = logger
|
|
|
|
# Make parameters a little shorter by making the following assignments.
|
|
if options.all_tests:
|
|
self.suite = 'suite_All'
|
|
else:
|
|
self.suite = self.options.suite
|
|
|
|
self.autotest_root = self.options.autotest_dir
|
|
self.debug = self.options.debug
|
|
self.docversion = self.options.docversion
|
|
self.doxyconf = self.options.doxyconf
|
|
self.html = '%s_%s' % (self.suite, self.options.html)
|
|
self.latex = self.options.latex
|
|
self.layout = self.options.layout
|
|
self.logfile = self.options.logfile
|
|
self.readme = self.options.readme
|
|
self.src_tests = '%s_%s' % (self.suite, self.options.src_tests)
|
|
|
|
self.testcase = {}
|
|
self.testcase_src = {}
|
|
|
|
self.site_dir = os.path.join(self.autotest_root, 'client', 'site_tests')
|
|
self.test_dir = os.path.join(self.autotest_root, 'client', 'tests')
|
|
self.suite_dir = os.path.join(self.site_dir, self.suite)
|
|
|
|
self.logger.debug('Executing with debug level: %s', self.debug)
|
|
self.logger.debug('Writing to logfile: %s', self.logfile)
|
|
self.logger.debug('New test directory: %s', self.src_tests)
|
|
self.logger.debug('Test suite: %s', self.suite)
|
|
|
|
self.suitename = {
|
|
'suite_All': 'All Existing Autotest Tests',
|
|
'suite_Factory': 'Factory Testing',
|
|
'suite_HWConfig': 'Hardware Configuration',
|
|
'suite_HWQual': 'Hardware Qualification',
|
|
}
|
|
|
|
def GetAllTests(self):
|
|
"""Create list of all discovered tests."""
|
|
for path in [ 'server/tests', 'server/site_tests', 'client/tests',
|
|
'client/site_tests']:
|
|
test_path = os.path.join(self.autotest_root, path)
|
|
if not os.path.exists(test_path):
|
|
continue
|
|
self.logger.info("Scanning %s", test_path)
|
|
tests, tests_src = fs_find_tests.GetTestsFromFS(test_path,
|
|
self.logger)
|
|
test_intersection = set(self.testcase) & set(tests)
|
|
if test_intersection:
|
|
self.logger.warning("Duplicates found: %s", test_intersection)
|
|
self.testcase.update(tests)
|
|
self.testcase_src.update(tests_src)
|
|
|
|
def GetTestsFromSuite(self):
|
|
"""Create list of tests invoked by a suite."""
|
|
|
|
suite_search = os.path.join(self.suite_dir, 'control.*')
|
|
for suitefile in glob.glob(suite_search):
|
|
self.logger.debug('Scanning %s for tests', suitefile)
|
|
if os.path.isfile(suitefile):
|
|
try:
|
|
suite = compiler.parseFile(suitefile)
|
|
except SyntaxError, e:
|
|
self.logger.error('Error parsing (gettests): %s\n%s',
|
|
suitefile, e)
|
|
raise SystemExit
|
|
|
|
# Walk through each node found in the control file, which in our
|
|
# case will be a call to a test. compiler.walk() will walk through
|
|
# each component node, and call the appropriate function in class
|
|
# ReadNode. The returned key should be a string, and the name of a
|
|
# test. visitor.value should be any extra arguments found in the
|
|
# suite file that are used with that test case.
|
|
for n in suite.node.nodes:
|
|
visitor = ReadNode(suite=True)
|
|
compiler.walk(n, visitor)
|
|
if len(visitor.key) > 1:
|
|
filtered_input = ''
|
|
# Lines in value should start with ' -' for bullet item.
|
|
if visitor.value:
|
|
lines = visitor.value.split('\n')
|
|
for line in lines:
|
|
if line.startswith(' -'):
|
|
filtered_input += line + '\n'
|
|
# A test could be called multiple times, so see if the key
|
|
# already exists, and if so append the new value.
|
|
if visitor.key in self.testcase:
|
|
s = self.testcase[visitor.key] + filtered_input
|
|
self.testcase[visitor.key] = s
|
|
else:
|
|
self.testcase[visitor.key] = filtered_input
|
|
|
|
def GetTests(self):
|
|
"""Create dictionary of tests based on suite control file contents."""
|
|
if self.options.all_tests:
|
|
self.GetAllTests()
|
|
else:
|
|
self.GetTestsFromSuite()
|
|
|
|
def _CleanDir(self, directory):
|
|
"""Ensure the directory is available and empty.
|
|
|
|
Args:
|
|
directory: string, path of directory
|
|
"""
|
|
|
|
if os.path.isdir(directory):
|
|
try:
|
|
shutil.rmtree(directory)
|
|
except IOError, err:
|
|
self.logger.error('Error cleaning %s\n%s', directory, err)
|
|
try:
|
|
os.makedirs(directory)
|
|
except IOError, err:
|
|
self.logger.error('Error creating %s\n%s', directory, err)
|
|
self.logger.error('Check your permissions of %s', directory)
|
|
raise SystemExit
|
|
|
|
def LocateTest(self, test_name):
|
|
"""Determine the full path location of the test."""
|
|
if test_name in self.testcase_src:
|
|
return os.path.join(self.testcase_src[test_name], test_name)
|
|
|
|
test_dir = os.path.join(self.site_dir, test_name)
|
|
if not os.path.isdir(test_dir):
|
|
test_dir = os.path.join(self.test_dir, test_name)
|
|
if os.path.isdir(test_dir):
|
|
return test_dir
|
|
|
|
self.logger.warning('Cannot find test: %s', test)
|
|
return None
|
|
|
|
|
|
def ParseControlFiles(self):
|
|
"""Get docstrings from control files and add them to new test scripts.
|
|
|
|
This method will cycle through all of the tests and attempt to find
|
|
their control file. If found, it will parse the docstring from the
|
|
control file, add this to any parameters found in the suite file, and
|
|
add this combined docstring to the original test. These new tests will
|
|
be written in the self.src_tests directory.
|
|
"""
|
|
# Clean some target directories.
|
|
for d in [self.src_tests, self.html]:
|
|
self._CleanDir(d)
|
|
|
|
for test in self.testcase:
|
|
test_dir = self.LocateTest(test)
|
|
if test_dir:
|
|
control_file = os.path.join(test_dir, 'control')
|
|
test_file = os.path.join(test_dir, test + '.py')
|
|
docstring = self._GetDocString(control_file, test)
|
|
self._CreateTest(test_file, docstring, test)
|
|
|
|
def _GetDocString(self, control_file, test):
|
|
"""Get the docstrings from control file and join to suite file params.
|
|
|
|
Args:
|
|
control_file: string, absolute path to test control file.
|
|
test: string, name of test.
|
|
Returns:
|
|
string: combined docstring with needed markup language for doxygen.
|
|
"""
|
|
|
|
# Doxygen needs the @package marker.
|
|
package_doc = '## @package '
|
|
# To allow doxygen to use special commands, we must use # for comments.
|
|
comment = '# '
|
|
endlist = ' .\n'
|
|
control_dict = {}
|
|
output = []
|
|
temp = []
|
|
tempstring = ''
|
|
docstring = ''
|
|
keys = ['\\brief\n', '<H3>Pass/Fail Criteria:</H3>\n',
|
|
'<H3>Author</H3>\n', '<H3>Test Duration</H3>\n',
|
|
'<H3>Category</H3>\n', '<H3>Test Type</H3>\n',
|
|
'<H3>Test Class</H3>\n', '<H3>Notest</H3>\n',
|
|
]
|
|
|
|
if not os.path.isfile(control_file):
|
|
self.logger.error('Cannot find: %s', control_file)
|
|
return None
|
|
try:
|
|
control = compiler.parseFile(control_file)
|
|
except SyntaxError, e:
|
|
self.logger.error('Error parsing (docstring): %s\n%s',
|
|
control_file, e)
|
|
return None
|
|
|
|
for n in control.node.nodes:
|
|
visitor = ReadNode()
|
|
compiler.walk(n, visitor)
|
|
control_dict[visitor.key] = visitor.value
|
|
|
|
for k in keys:
|
|
if k in control_dict:
|
|
if len(control_dict[k]) > 1:
|
|
if k != test:
|
|
temp.append(k)
|
|
temp.append(control_dict[k])
|
|
if control_dict[k]:
|
|
temp.append(endlist)
|
|
# Add constraints and extra args after the Criteria section.
|
|
if 'Criteria:' in k:
|
|
if self.testcase[test]:
|
|
temp.append('<H3>Arguments:</H3>\n')
|
|
temp.append(self.testcase[test])
|
|
# '.' character at the same level as the '-' tells
|
|
# doxygen this is the end of the list.
|
|
temp.append(endlist)
|
|
|
|
output.append(package_doc + test + '\n')
|
|
tempstring = "".join(temp)
|
|
lines = tempstring.split('\n')
|
|
for line in lines:
|
|
# Doxygen requires a '#' character to add special doxygen commands.
|
|
comment_line = comment + line + '\n'
|
|
output.append(comment_line)
|
|
|
|
docstring = "".join(output)
|
|
|
|
return docstring
|
|
|
|
|
|
def _CreateTest(self, test_file, docstring, test):
|
|
"""Create a new test with the combined docstrings from multiple sources.
|
|
|
|
Args:
|
|
test_file: string, file name of new test to write.
|
|
docstring: string, the docstring to add to the existing test.
|
|
test: string, name of the test.
|
|
|
|
This method is used to create a temporary copy of a new test, that will
|
|
be a combination of the original test plus the docstrings from the
|
|
control file, and any constraints from the suite control file.
|
|
"""
|
|
|
|
class_def = 'class ' + test
|
|
pathname = os.path.join(self.src_tests, test + '.py')
|
|
|
|
# Open the test and write out new test with added docstrings
|
|
try:
|
|
f = open(test_file, 'r')
|
|
except IOError, err:
|
|
self.logger.error('Error while reading %s\n%s', test_file, err)
|
|
return
|
|
lines = f.readlines()
|
|
f.close()
|
|
|
|
try:
|
|
f = open(pathname, 'w')
|
|
except IOError, err:
|
|
self.logger.error('Error creating %s\n%s', pathname, err)
|
|
return
|
|
|
|
for line in lines:
|
|
if class_def in line and docstring:
|
|
f.write(docstring)
|
|
f.write('\n')
|
|
f.write(line)
|
|
f.close()
|
|
|
|
def CreateMainPage(self, current_dir):
|
|
"""Create a main page to provide content for index.html.
|
|
|
|
This method assumes a file named README.txt is located in your suite
|
|
directory with general instructions on setting up and using the suite.
|
|
If your README file is in another file, ensure you pass a --readme
|
|
option with the correct filename. To produce a better looking
|
|
landing page, use the '-' character for list items. This method assumes
|
|
os commands start with '$'.
|
|
"""
|
|
|
|
# Define some strings that Doxygen uses for specific formatting.
|
|
cstart = '/**'
|
|
cend = '**/'
|
|
mp = '@mainpage'
|
|
section_begin = '@section '
|
|
vstart = '@verbatim '
|
|
vend = ' @endverbatim\n'
|
|
|
|
# Define some characters we expect to delineate sections in the README.
|
|
sec_char = '=========='
|
|
command_prompt = '$ '
|
|
crosh_prompt = 'crosh>'
|
|
command_cont = '\\'
|
|
|
|
command = False
|
|
comment = False
|
|
section = False
|
|
sec_ctr = 0
|
|
|
|
if self.options.all_tests:
|
|
readme_file = os.path.join(current_dir, self.readme)
|
|
else:
|
|
readme_file = os.path.join(self.suite_dir, self.readme)
|
|
mainpage_file = os.path.join(self.src_tests, 'mainpage.txt')
|
|
|
|
try:
|
|
f = open(readme_file, 'r')
|
|
except IOError, err:
|
|
self.logger.error('Error opening %s\n%s', readme_file, err)
|
|
return
|
|
try:
|
|
fw = open(mainpage_file, 'w')
|
|
except IOError, err:
|
|
self.logger.error('Error opening %s\n%s', mainpage_file, err)
|
|
return
|
|
|
|
lines = f.readlines()
|
|
f.close()
|
|
|
|
fw.write(cstart)
|
|
fw.write('\n')
|
|
fw.write(mp)
|
|
fw.write('\n')
|
|
|
|
for line in lines:
|
|
if sec_char in line:
|
|
comment = True
|
|
section = not section
|
|
elif section:
|
|
sec_ctr += 1
|
|
section_name = ' section%d ' % sec_ctr
|
|
fw.write(section_begin + section_name + line)
|
|
else:
|
|
# comment is used to denote when we should start recording text
|
|
# from the README file. Some of the initial text is not needed.
|
|
if comment:
|
|
if command_prompt in line or crosh_prompt in line:
|
|
line = line.rstrip()
|
|
if line[-1] == command_cont:
|
|
fw.write(vstart + line[:-1])
|
|
command = True
|
|
else:
|
|
fw.write(vstart + line + vend)
|
|
elif command:
|
|
line = line.strip()
|
|
if line[-1] == command_cont:
|
|
fw.write(line)
|
|
else:
|
|
fw.write(line + vend)
|
|
command = False
|
|
else:
|
|
fw.write(line)
|
|
|
|
fw.write('\n')
|
|
fw.write(cend)
|
|
fw.close()
|
|
|
|
def _ConfigDoxygen(self):
|
|
"""Set Doxygen configuration to match our options."""
|
|
|
|
doxy_config = {
|
|
'ALPHABETICAL_INDEX': 'YES',
|
|
'EXTRACT_ALL': 'YES',
|
|
'EXTRACT_LOCAL_METHODS': 'YES',
|
|
'EXTRACT_PRIVATE': 'YES',
|
|
'EXTRACT_STATIC': 'YES',
|
|
'FILE_PATTERNS': '*.py *.txt',
|
|
'FULL_PATH_NAMES ': 'YES',
|
|
'GENERATE_TREEVIEW': 'YES',
|
|
'HTML_DYNAMIC_SECTIONS': 'YES',
|
|
'HTML_FOOTER': 'footer.html',
|
|
'HTML_HEADER': 'header.html',
|
|
'HTML_OUTPUT ': self.html,
|
|
'INLINE_SOURCES': 'YES',
|
|
'INPUT ': self.src_tests,
|
|
'JAVADOC_AUTOBRIEF': 'YES',
|
|
'LATEX_OUTPUT ': self.latex,
|
|
'LAYOUT_FILE ': self.layout,
|
|
'OPTIMIZE_OUTPUT_JAVA': 'YES',
|
|
'PROJECT_NAME ': self.suitename[self.suite],
|
|
'PROJECT_NUMBER': self.docversion,
|
|
'SOURCE_BROWSER': 'YES',
|
|
'STRIP_CODE_COMMENTS': 'NO',
|
|
'TAB_SIZE': '4',
|
|
'USE_INLINE_TREES': 'YES',
|
|
}
|
|
|
|
doxy_layout = {
|
|
'tab type="mainpage"': 'title="%s"' %
|
|
self.suitename[self.suite],
|
|
'tab type="namespaces"': 'title="Tests"',
|
|
'tab type="namespacemembers"': 'title="Test Functions"',
|
|
}
|
|
|
|
for line in fileinput.input(self.doxyconf, inplace=1):
|
|
for k in doxy_config:
|
|
if line.startswith(k):
|
|
line = '%s = %s\n' % (k, doxy_config[k])
|
|
sys.stdout.write(line)
|
|
|
|
for line in fileinput.input('header.html', inplace=1):
|
|
if line.startswith('<H2>'):
|
|
line = '<H2>%s</H2>\n' % self.suitename[self.suite]
|
|
sys.stdout.write(line)
|
|
|
|
for line in fileinput.input(self.layout, inplace=1):
|
|
for k in doxy_layout:
|
|
if line.find(k) != -1:
|
|
line = line.replace('title=""', doxy_layout[k])
|
|
sys.stdout.write(line.rstrip() + '\n')
|
|
|
|
def RunDoxygen(self, doxyargs):
|
|
"""Execute Doxygen on the files in the self.src_tests directory.
|
|
|
|
Args:
|
|
doxyargs: string, any command line args to be passed to doxygen.
|
|
"""
|
|
|
|
doxycmd = 'doxygen %s' % doxyargs
|
|
|
|
p = subprocess.Popen(doxycmd, shell=True, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
stdout, stderr = p.communicate()
|
|
if p.returncode:
|
|
self.logger.error('Error while running %s', doxycmd)
|
|
self.logger.error(stdout)
|
|
self.logger.error(stderr)
|
|
else:
|
|
self.logger.info('%s successfully ran', doxycmd)
|
|
|
|
def CreateDocs(self):
|
|
"""Configure and execute Doxygen to create HTML docuements."""
|
|
|
|
# First run doxygen with args to create default configuration files.
|
|
# Create layout xml file.
|
|
doxyargs = '-l %s' % self.layout
|
|
self.RunDoxygen(doxyargs)
|
|
|
|
# Create doxygen configuration file.
|
|
doxyargs = '-g %s' % self.doxyconf
|
|
self.RunDoxygen(doxyargs)
|
|
|
|
# Edit the configuration files to match our options.
|
|
self._ConfigDoxygen()
|
|
|
|
# Run doxygen with configuration file as argument.
|
|
self.RunDoxygen(self.doxyconf)
|
|
|
|
def PostProcessDocs(self, current_dir):
|
|
"""Run some post processing on the newly created docs."""
|
|
|
|
# Key = original string, value = replacement string.
|
|
replace = {
|
|
'>Package': '>Test',
|
|
}
|
|
|
|
docpages = os.path.join(self.html, '*.html')
|
|
files = glob.glob(docpages)
|
|
for file in files:
|
|
for line in fileinput.input(file, inplace=1):
|
|
for k in replace:
|
|
if line.find(k) != -1:
|
|
line = line.replace(k, replace[k])
|
|
print line,
|
|
|
|
logo_image = 'customLogo.gif'
|
|
html_root = os.path.join(current_dir, self.html)
|
|
shutil.copy(os.path.join(current_dir, logo_image), html_root)
|
|
|
|
# Copy under dashboard.
|
|
if self.options.dashboard:
|
|
dashboard_root = os.path.join(self.autotest_root, 'results',
|
|
'dashboard', 'testdocs')
|
|
if not os.path.isdir(dashboard_root):
|
|
try:
|
|
os.makedirs(dashboard_root)
|
|
except e:
|
|
self.logger.error('Error creating %s:%s', dashboard_root, e)
|
|
return
|
|
os.system('cp -r %s/* %s' % (html_root, dashboard_root))
|
|
os.system('find %s -type d -exec chmod 755 {} \;' % dashboard_root)
|
|
os.system('find %s -type f -exec chmod 644 {} \;' % dashboard_root)
|
|
|
|
self.logger.info('Sanitized documentation completed.')
|
|
|
|
|
|
class ReadNode(object):
|
|
"""Parse a compiler node object from a control file.
|
|
|
|
Args:
|
|
suite: boolean, set to True if parsing nodes from a suite control file.
|
|
"""
|
|
|
|
def __init__(self, suite=False):
|
|
self.key = ''
|
|
self.value = ''
|
|
self.testdef = False
|
|
self.suite = suite
|
|
self.bullet = ' - '
|
|
|
|
def visitName(self, n):
|
|
if n.name == 'job':
|
|
self.testdef = True
|
|
|
|
def visitConst(self, n):
|
|
if self.testdef:
|
|
self.key = str(n.value)
|
|
self.testdef = False
|
|
else:
|
|
self.value += str(n.value) + '\n'
|
|
|
|
def visitKeyword(self, n):
|
|
if n.name != 'constraints':
|
|
self.value += self.bullet + n.name + ': '
|
|
for item in n.expr:
|
|
if isinstance(item, compiler.ast.Const):
|
|
for i in item:
|
|
self.value += self.bullet + str(i) + '\n'
|
|
self.value += ' .\n'
|
|
else:
|
|
self.value += str(item) + '\n'
|
|
|
|
|
|
def visitAssName(self, n):
|
|
# To remove section from appearing in the documentation, set value = ''.
|
|
sections = {
|
|
'AUTHOR': '',
|
|
'CRITERIA': '<H3>Pass/Fail Criteria:</H3>\n',
|
|
'DOC': '<H3>Notes</H3>\n',
|
|
'NAME': '',
|
|
'PURPOSE': '\\brief\n',
|
|
'TIME': '<H3>Test Duration</H3>\n',
|
|
'TEST_CATEGORY': '<H3>Category</H3>\n',
|
|
'TEST_CLASS': '<H3>Test Class</H3>\n',
|
|
'TEST_TYPE': '<H3>Test Type</H3>\n',
|
|
}
|
|
|
|
if not self.suite:
|
|
self.key = sections.get(n.name, n.name)
|
|
|
|
|
|
def ParseOptions(current_dir):
|
|
"""Common processing of command line options."""
|
|
|
|
desc="""%prog will scan AutoTest suite control files to build a list of
|
|
test cases called in the suite, and build HTML documentation based on
|
|
the docstrings it finds in the tests, control files, and suite control
|
|
files.
|
|
"""
|
|
parser = optparse.OptionParser(description=desc,
|
|
prog='CreateDocs',
|
|
version=__version__,
|
|
usage='%prog')
|
|
parser.add_option('--alltests',
|
|
help='Scan for all tests',
|
|
action='store_true',
|
|
default=False,
|
|
dest='all_tests')
|
|
parser.add_option('--autotest_dir',
|
|
help='path to autotest root directory'
|
|
' [default: %default]',
|
|
default=None,
|
|
dest='autotest_dir')
|
|
parser.add_option('--dashboard',
|
|
help='Copy output under dashboard',
|
|
action='store_true',
|
|
default=False,
|
|
dest='dashboard')
|
|
parser.add_option('--debug',
|
|
help='Debug level [default: %default]',
|
|
default='debug',
|
|
dest='debug')
|
|
parser.add_option('--docversion',
|
|
help='Specify a version for the documentation'
|
|
'[default: %default]',
|
|
default=None,
|
|
dest='docversion')
|
|
parser.add_option('--doxy',
|
|
help='doxygen configuration file [default: %default]',
|
|
default=os.path.join(current_dir, 'doxygen.conf'),
|
|
dest='doxyconf')
|
|
parser.add_option('--html',
|
|
help='path to store html docs [default: %default]',
|
|
default='html',
|
|
dest='html')
|
|
parser.add_option('--latex',
|
|
help='path to store latex docs [default: %default]',
|
|
default='latex',
|
|
dest='latex')
|
|
parser.add_option('--layout',
|
|
help='doxygen layout file [default: %default]',
|
|
default=os.path.join(current_dir, 'doxygenLayout.xml'),
|
|
dest='layout')
|
|
parser.add_option('--log',
|
|
help='Logfile for program output [default: %default]',
|
|
default=os.path.join(current_dir, 'docCreator.log'),
|
|
dest='logfile')
|
|
parser.add_option('--readme',
|
|
help='filename of suite documentation'
|
|
'[default: %default]',
|
|
default='README.txt',
|
|
dest='readme')
|
|
parser.add_option('--suite',
|
|
help='Directory name of suite [default: %default]',
|
|
type='choice',
|
|
default='suite_HWQual',
|
|
choices = [
|
|
'suite_Factory',
|
|
'suite_HWConfig',
|
|
'suite_HWQual',
|
|
],
|
|
dest='suite')
|
|
parser.add_option('--tests',
|
|
help='Absolute path of temporary test files'
|
|
' [default: %default]',
|
|
default='testsource',
|
|
dest='src_tests')
|
|
return parser.parse_args()
|
|
|
|
|
|
def CheckOptions(options, logger):
|
|
"""Verify required command line options."""
|
|
|
|
if not options.autotest_dir:
|
|
logger.error('You must supply --autotest_dir')
|
|
raise SystemExit
|
|
|
|
if not os.path.isfile(options.doxyconf):
|
|
logger.error('Unable to locate --doxy: %s', options.doxyconf)
|
|
raise SystemExit
|
|
|
|
if not os.path.isfile(options.layout):
|
|
logger.error('Unable to locate --layout: %s', options.layout)
|
|
raise SystemExit
|
|
|
|
|
|
def SetLogger(namespace, options):
|
|
"""Create a logger with some good formatting options.
|
|
|
|
Args:
|
|
namespace: string, name associated with this logger.
|
|
Returns:
|
|
Logger object.
|
|
This method assumes logfile and debug are already set.
|
|
This logger will write to stdout as well as a log file.
|
|
"""
|
|
|
|
loglevel = {'debug': logging.DEBUG,
|
|
'info': logging.INFO,
|
|
'warning': logging.WARNING,
|
|
'error': logging.ERROR,
|
|
'critical': logging.CRITICAL,
|
|
}
|
|
|
|
logger = logging.getLogger(namespace)
|
|
c = logging.StreamHandler()
|
|
h = logging.FileHandler(
|
|
os.path.join(os.path.abspath('.'), options.logfile))
|
|
hf = logging.Formatter(
|
|
'%(asctime)s %(process)d %(levelname)s: %(message)s')
|
|
cf = logging.Formatter('%(levelname)s: %(message)s')
|
|
logger.addHandler(h)
|
|
logger.addHandler(c)
|
|
h.setFormatter(hf)
|
|
c.setFormatter(cf)
|
|
|
|
logger.setLevel(loglevel.get(options.debug, logging.INFO))
|
|
|
|
return logger
|
|
|
|
|
|
def main():
|
|
current_dir = os.path.dirname(sys.argv[0])
|
|
options, args = ParseOptions(current_dir)
|
|
logger = SetLogger('docCreator', options)
|
|
CheckOptions(options, logger)
|
|
doc = DocCreator(options, args, logger)
|
|
doc.GetTests()
|
|
doc.ParseControlFiles()
|
|
doc.CreateMainPage(current_dir)
|
|
doc.CreateDocs()
|
|
doc.PostProcessDocs(current_dir)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|