#!/usr/bin/python -u
#
# $Id: test_happydoc.py,v 1.39 2001/04/15 18:46:16 doughellmann Exp $
#
# Time-stamp: <01/04/15 14:44:09 dhellmann>
#
# Copyright Doug Hellmann 2000
#
#                         All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#

"""Driver for unit tests for HappyDoc.

"""

__rcs_info__ = {
    #
    #  Creation Information
    #
    'module_name'  : '$RCSfile: test_happydoc.py,v $',
    'rcs_id'       : '$Id: test_happydoc.py,v 1.39 2001/04/15 18:46:16 doughellmann Exp $',
    'creator'      : 'Doug Hellmann <doughellmann@bigfoot.com>',
    'project'      : 'HappyDoc',
    'created'      : 'Sun, 13-Aug-2000 10:16:13 EDT',

    #
    #  Current Information
    #
    'author'       : '$Author: doughellmann $',
    'version'      : '$Revision: 1.39 $',
    'date'         : '$Date: 2001/04/15 18:46:16 $',
}

#
# Import system modules
#
import sys
import os
import tempfile
import string
from glob import glob
import pprint

#
# Local Modules
#
from happydoc_class import HappyDoc
import hdpath

#
# Fix the import path so that we can get to the unit test module.
#
sys.path.append('../../Python/pyunit')

import unittest


#
# Import Local modules
#
import CommandLineApp

#
# Module
#

verboseLevel=1

class HappyDocTest(unittest.TestCase):

    def __init__(self,
                 outputDir='DefaultTestOutputDir',
                 methodName='runTest',
                 ):
        unittest.TestCase.__init__(self, methodName)
        self.name=methodName
        self.output_dir = outputDir
        return

    def setUp(self):
        self.happydoc = './happydoc'
        return

    def runHappyDoc(self, testName, modules=(), extraArgs=()):
        #
        # Fix up output directory variable
        #
        output_dir = self.output_dir
        happydoc = self.happydoc
        output_dir = '%(output_dir)s/%(testName)s' % locals()
        #
        # Verbose level setting
        #
        global verboseLevel
        if verboseLevel:
            verboseArgs = '-%s' % ('v' * verboseLevel)
        else:
            verboseArgs = ''
        #
        # Build the argument list for the command.
        #
        # We have to pay some attention to the
        # order in which values are added to the
        # list, to ensure all options are presented
        # before arguments (so getopt will not
        # interpret arguments as filenames)
        #
        argv = [ verboseArgs,
                 '-t', testName,
                 '-d', output_dir,
                 ] + \
                 \
                 list(extraArgs) + \
                 \
                 [ 'formatter_dateStampFiles=0',  # always different, breaks diff
                   ] + \
                   \
                   list(modules)

        print
        print
        print 'Arguments: ',
        pprint.pprint(argv)
        print
        sys.stdout.flush()
        #
        # Execute the test
        #
        try:
            HappyDoc(argv).run()
        except HappyDoc.HelpRequested:
            pass
        #
        # Flush the output so watchers can keep up
        #
        sys.stdout.flush()
        sys.stderr.flush()
        return
    
    def checkHelpSyntax(self):
        'Basic help syntax'
        assert not self.runHappyDoc('checkHelpSyntax', (),
                                    extraArgs=('-h',)
                                    ), 'Basic help syntax test failed.'
        return
 
    def checkHelpManual(self):
        'Extended help manual'
        assert not self.runHappyDoc('checkHelpManual', (),
                                    extraArgs=('--help',)
                                    ), 'Help manual generation test failed.'
        return
 
    def checkHelpNoArgs(self):
        'Extended help when no args are specified'
        assert not self.runHappyDoc('checkHelpNoArgs', (),
                                    extraArgs=()
                                    ), 'Help manual generation test failed.'
        return

    def checkBasicParser(self):
        'Python parser/info extraction test'
        assert (not self.runHappyDoc('checkBasicParser',
                                     ('TestCases/test.py',
                                      'TestCases/test_just_docstring.py',
                                      ),
                                     )
                ), 'Basic parser test failed.'
        return
 
    def checkSingleFileDocset(self):
        'Single file docset'
        assert (not self.runHappyDoc('checkSingleFileDocset',
                                     ('TestCases/test.py',),
                                     extraArgs=('-p', '-', '-T', 'singlefile')
                                     )
                ), 'Basic single-file docset test failed.'
        return

    def checkTextStdoutDocset(self):
        'Standard output docset with text formatting'
        assert (not self.runHappyDoc('checkTextStdoutDocset',
                                     ('TestCases/test.py',),
                                     extraArgs=('-F', 'text', '-T', 'stdout')
                                     )
                ), 'Text to standard-output docset test failed.'
        return

    def checkHTMLStdoutDocset(self):
        'Standard output docset with HTML formatting'
        assert (not self.runHappyDoc('checkHTMLStdoutDocset',
                                     ('TestCases/test.py',),
                                     extraArgs=('-T', 'stdout')
                                     )
                ), 'HTML to standard-output docset test failed.'
        return

    def checkTextTestModule(self):
        'Text formatter'
        assert (not self.runHappyDoc('checkTextTestModule',
                                     ('TestCases/test.py',),
                                     extraArgs=('-F', 'text')
                                     )
                ), 'Text formatter test failed.'
        return
    
    def checkBasicTwoModules(self):
        'References between two related modules'
        assert (not self.runHappyDoc('checkBasicTwoModules',
                                     ('TestCases/test.py', 'CommandLineApp.py')
                                     )
                ), 'Two related modules test failed.'
        return
    
    def checkBasicEmptyModule(self):
        assert (not self.runHappyDoc('checkBasicEmptyModule',
                                     ('TestCases/emptytest.py',)
                                     )
                ), 'Empty module test failed.'
        return

    def checkBasicImports(self):
        assert (not self.runHappyDoc('checkBasicImports',
                                     ('TestCases/test_import_statements.py',
                                      'CommandLineApp.py',
                                      'prettyast.py',
                                      'parseinfo.py'),
                                     extraArgs=('-p', '')
                                     )
                ), 'Imported module id test failed.'
        return

    def checkBasicImportsFromPackages(self):
        assert (not self.runHappyDoc('checkBasicImportsFromPackages',
                                     ('TestCases/test_import_packages',),
                                     )
                ), 'Import from packages test failed.'
        return

    def checkPackageSummaries(self):
        assert (not self.runHappyDoc('checkPackageSummaries',
                                     ('TestCases/test_package_summaries',),
                                     )
                ), 'Package summaries test failed.'
        return

    def checkBasicImportsFromPackagesIgnorePackages(self):
        assert (not self.runHappyDoc('checkBasicImportsFromPackagesIgnorePackages',
                                     ('TestCases/test_import_packages',),
                                     extraArgs=('docset_usePackages=0',),
                                     )
                ), 'Import from packages while ignoring package special handling test failed.'
        return

    def checkImportsFromPackagesSimple(self):
        assert (not self.runHappyDoc('checkImportsFromPackagesSimple',
                                     ('TestCases/test_import_small_set',),
                                     )
                ), 'Import from small set of packages test failed.'
        return

    def checkOutputPrefix(self):
        assert (not self.runHappyDoc('checkOutputPrefix',
                                     ('TestCases/test_import_packages',),
                                     extraArgs=('-p',
                                                '',
                                                'formatter_filenamePrefix=TESTPREFIX_')
                                     )
                ), 'Formatter output prefix test failed.'
        return

    def checkBasicFunctionParameters(self):
        assert (not self.runHappyDoc('checkBasicFunctionParameters',
                                     ('TestCases/test_function_params.py',
                                      ),
                                     extraArgs=('-p', '')
                                     )
                ), 'Function parameter test failed.'
        return

    def checkBasicStructuredText(self):
        assert (not self.runHappyDoc('checkBasicStructuredText',
                                     ('TestCases/test_make_hlink.py',
                                      ),
                                     extraArgs=('-p', '')
                                     )
                ), 'StructuredText hyperlink test failed.'
        return
    
    def checkBasicReadme(self):
        assert (not self.runHappyDoc('checkBasicReadme')), 'README test failed.'
        return
    
    def checkSelfDocumentation(self):
        assert (not self.runHappyDoc('checkSelfDocumentation',
                                     ('../HappyDoc',))
                ), 'Full self-documentation test failed.'
        return
    
    def checkSelfDocumentationCompact(self):
        assert (not self.runHappyDoc('checkSelfDocumentationCompact',
                                     ('../HappyDoc',),
                                     extraArgs=('hdformatter_compactHTML=yes',))
                ), 'Full self-documentation with compact output test failed.'
        return
    
    def checkSelfDocumentationDocBook(self):
        assert (not self.runHappyDoc('checkSelfDocumentationDocBook',
                                     ('../HappyDoc',),
                                     extraArgs=('-F', 'sgmldocbook'))
                ), 'Full self-documentation in DocBook format output test failed.'
        return
    
    def checkSelfDocumentationDocBookSingleFile(self):
        assert (not self.runHappyDoc('checkSelfDocumentationDocBookSingleFile',
                                     ('../HappyDoc',),
                                     extraArgs=('-F',
                                                'sgmldocbook',
                                                '-T',
                                                'singlefile'))
                ), 'Full self-documentation in DocBook format output test failed.'
        return
    
    def checkSelfDocumentationNoDescription(self):
        assert (not self.runHappyDoc('checkSelfDocumentationNoDescription',
                                     ('../HappyDoc',),
                                     extraArgs=('-p', '-'))
                ), 'Full self-documentation test failed.'
        return

    def checkSelfDocumentationAsText(self):
        assert (not self.runHappyDoc('checkSelfDocumentationAsText',
                                     ('../HappyDoc',),
                                     extraArgs=('-F', 'text'))
                ), 'Full self-documentation-as-text test failed.'
        return

    def checkPrivateNames(self):
        assert (not self.runHappyDoc('checkPrivateNames',
                                     ('TestCases/test_private_names.py',),
                                     extraArgs=('--no_private_names', '-p', ''))
                ), 'Documentation without showing private names.'
        return

    def checkIgnoreComments(self):
        assert (not self.runHappyDoc('checkIgnoreComments',
                                     ('TestCases/test_ignore_comments.py',),
                                     extraArgs=('--no_comments', '-p', ''))
                ), 'Documentation without showing comments as __doc__ strings.'
        return

    def checkNestedStructures(self):
        assert (not self.runHappyDoc('checkNestedStructures',
                                     ('TestCases/test_nested_structures.py',),
                                     extraArgs=('-p', ''))
                ), 'Nested class and function test.'
        return

    def checkIgnoreDirectories(self):
        assert (not self.runHappyDoc('checkIgnoreDirectories',
                                     ('../HappyDoc',),
                                     extraArgs=( '-i', 'docset',
                                                 '-i', 'hdformatter',
                                                 '-i', 'TestCases',))
                ), 'Full self-documentation test failed.'
        return
    

class OtherWorkingDirTest(HappyDocTest):

    def __init__(self,
                 workingDir='.',
                 outputDir='DefaultTestOutputDir',
                 **nargs
                 ):
        self.dir_stack = None
        self.working_dir = workingDir
        apply(HappyDocTest.__init__, (self,), nargs)
        self.output_dir = hdpath.join(os.pardir, 'HappyDoc', outputDir)
        return

    def setUp(self):
        self.happydoc = '../HappyDoc/happydoc'
        return
    
    def runHappyDoc(self, *args, **nargs):
        self.pushDir()
        apply(HappyDocTest.runHappyDoc, (self,) + args, nargs)
        self.popDir()
        return

    def pushDir(self):
        self.dir_stack = (os.getcwd(), self.dir_stack)
        os.chdir(self.working_dir)
        return

    def popDir(self):
        if self.dir_stack:
            top, self.dir_stack = self.dir_stack
            os.chdir(top)
        return

class ExternalTest(OtherWorkingDirTest):

    def externalApp(self, command):
        ret = os.system('python %s' % command)
        assert not ret

    def checkPluginLoader(self):
        self.externalApp('./TestCases/test_plugin_loader/runtest.py')
        return

class ZopeTest(OtherWorkingDirTest):
    
    def checkZopeFull(self):
        assert (not self.runHappyDoc('checkZopeFull',
                                     ('../Zope-2-CVS-src',),
                                     )
                ), 'Zope full documentation test failed.'
        return
    
    def checkZopeRoot(self):
        assert (not self.runHappyDoc('checkZopeRoot',
                                     ('../Zope-2-CVS-src',),
                                     extraArgs=('-r',))
                ), 'Zope full documentation test failed.'
        return

    def checkGadflyParseError(self):
        assert (not self.runHappyDoc('checkGadflyParseError',
                                     ('../Zope-2-CVS-src/lib/python/Products/ZGadflyDA/gadfly/gfdb0.py',),
                                     extraArgs=('-r',))
                ), 'Gadfly test with parse-error failed.'
        return

    def checkZEOParseError(self):
        assert (not self.runHappyDoc('checkZEOParseError',
                                     ('../Zope-2-CVS-src/lib/python/ZEO/zrpc.py',),
                                     extraArgs=('-r',))
                ), 'ZEO test with parse-error failed.'
        return

    def checkZopeWithSafePrefix(self):
        assert (not self.runHappyDoc('checkZopeWithSafePrefix',
                                     ('../Zope-2-CVS-src',),
                                     extraArgs=('formatter_filenamePrefix=zsd_',))
                ), 'Zope test with output prefix failed.'
        return
        
        
        
def ZopeTestFactory(**nargs):
    nargs['workingDir'] = nargs.get('workingDir', '../Zope-2-CVS-src')
    return apply(ZopeTest, (), nargs)

class HappyDocBugRegressionTest(HappyDocTest):

    def __init__(self,
                 outputDir='DefaultTestOutputDir',
                 methodName='',
                 ):
        HappyDocTest.__init__(self,
                              outputDir=outputDir,
                              methodName='checkBugReport%s' % methodName)
        return

    def checkBugReport(self, bugId):
        assert not self.runHappyDoc(bugId, ('TestCases/test_bug%s.py' % bugId, ),
                                    extraArgs=('-p', '-'),
                                    ), 'Check for bug %s failed.' % bugId

    def __getattr__(self, name):
        if name[:14] == 'checkBugReport':
            id = name[14:]
            test_func = lambda bug=id, s=self: s.checkBugReport(bug)
            test_func.__doc__ = 'Regression test for bug %s' % id
            return test_func
        raise AttributeError(name)


    
test_definitions = [

        #
        # Test help generation
        #
        ( 'checkHelpSyntax', HappyDocTest ),
        ( 'checkHelpManual', HappyDocTest ),
        ( 'checkHelpNoArgs', HappyDocTest ),
        
        #
        # Test basic pieces of the parser and formatters
        #
        ( 'checkBasicParser',                            HappyDocTest ),
        ( 'checkBasicReadme',                            HappyDocTest ),
        ( 'checkBasicTwoModules',                        HappyDocTest ),
        ( 'checkBasicImports',                           HappyDocTest ),
        ( 'checkBasicFunctionParameters',                HappyDocTest ),
        ( 'checkPrivateNames',                           HappyDocTest ),
        ( 'checkIgnoreComments',                         HappyDocTest ),
        ( 'checkBasicEmptyModule',                       HappyDocTest ),
        ( 'checkBasicImportsFromPackages',               HappyDocTest ),
        ( 'checkBasicImportsFromPackagesIgnorePackages', HappyDocTest ),

        #
        # Quick tests
        #
        ( 'checkBasicParser', HappyDocTest ),

        #
        # Test processing the HappyDoc code and generating docs
        #
        ( 'checkSelfDocumentation',              HappyDocTest ),
        ( 'checkSelfDocumentationNoDescription', HappyDocTest ),
        ( 'checkSelfDocumentationCompact',       HappyDocTest ),

        #
        # Test the formatter_textfile.py module.
        #
        ( 'checkTextTestModule',          HappyDocTest ),
        ( 'checkSelfDocumentationAsText', HappyDocTest ),

        #
        # Test the various docset formats
        #
        ( 'checkSingleFileDocset', HappyDocTest ),
        ( 'checkTextStdoutDocset', HappyDocTest ),
        ( 'checkHTMLStdoutDocset', HappyDocTest ),

        #
        # Test the formatter
        #
        ('checkBasicStructuredText', HappyDocTest),

        #
        # The hdformatter filename prefix test suite
        #
        ( 'checkOutputPrefix', HappyDocTest ),

        #
        # Packages
        #
        ( 'checkBasicImportsFromPackages', HappyDocTest ),
        ( 'checkPackageSummaries',         HappyDocTest ),

        #
        # DocBook
        #
        ( 'checkSelfDocumentationDocBook',           HappyDocTest ),
        ( 'checkSelfDocumentationDocBookSingleFile', HappyDocTest ),

        #
        # External tests
        #
        ('checkPluginLoader', ExternalTest),

        #
        # Ignore directories
        #
        ('checkIgnoreDirectories', HappyDocTest ),


        ]

    
def makeTestSuite(includeZope=0,
                  outputDir='../HappyDocRegressionTest/SimpleTestOutput',
                  ):
    "Returns the test suite."
    if includeZope:
        #
        # Test against the Zope source code
        #
        # These tests take a *LONG* time, so they are
        # not generally run.
        #
        test_definitions.append( ('checkZopeRoot',      ZopeTestFactory) )
        test_definitions.append( ('checkZopeFull',      ZopeTestFactory) )
        test_definitions.append( ('checkZEOParseError', ZopeTestFactory) )
        
        #
        # Test against the Gadfly code which has
        # parse errors.
        #
        test_definitions.append( ('checkGadflyParseError', ZopeTestFactory) )

        #
        # Test output Zope docs with a prefix that would allow the files
        # to be loaded to Zope.org.
        #
        test_definitions.append( ('checkZopeWithSafePrefix', ZopeTestFactory) )
    #
    # Test suite to run all tests
    #
    all_test_suite = unittest.TestSuite()
    all_test_suite.byName = {}
    add_all_tests = all_test_suite.addTest
    
    #
    # Check tests related to bug reports
    #
    bug_ids = map(lambda x:x[18:-3], glob('TestCases/test_bug*.py'))
    bug_ids.sort()
    for bug in bug_ids:
        test_definitions.append( (bug, HappyDocBugRegressionTest) )

    #
    # Instantiate the tests
    #
    for test_name, test_factory in test_definitions:
        new_test = test_factory( outputDir=outputDir,
                                 methodName=test_name,
                                 )
        all_test_suite.byName[ test_name ] = new_test
        add_all_tests( new_test )
    return all_test_suite

    
class TestCaseDriver(CommandLineApp.CommandLineApp):
    "Drive the test cases for HappyDoc."

    LIST = 'list'
    RUNTEST = 'run'

    _include_zope = 0
    _output_dir = 'DefaultTestOutputDir'
    _operation = RUNTEST

    _default_test_case = 'checkBasicParser'

    def optionHandler_v(self):
        "Increase verbose level by one.  Can be repeated."
        global verboseLevel
        verboseLevel = verboseLevel + 1
        return

    def optionHandler_withzope(self):
        "Add the Zope tests to the set."
        self._include_zope = 1
        return

    def optionHandler_d(self, outputDir):
        "Specify the output directory for the tests."
        self._output_dir = outputDir
        return

    def optionHandler_list(self):
        "List the tests available."
        self._operation = self.LIST
        return

    def optionHandler_t(self, testSetName):
        "Specify a testSetName to run."
        if ( (len(self._desired_tests) == 1)
             and
             (self._desired_tests[0] == self._default_test_case)
             ):
            self._desired_tests = []
        self._desired_tests.append(string.strip(testSetName))
        return

    def appInit(self):
        self._desired_tests = []
        self.optionHandler_t(self._default_test_case)
        self.optionHandler_d('../HappyDocRegressionTest/SimpleTestOutput')
        return

    def buildTests(self):
        "Create the test suites."
        self._suites = makeTestSuite(self._include_zope,
                                     self._output_dir)
        return

    def runTests(self, suite):
        "Run the required test cases."
        #
        # Run the test suite
        #

        print '=' * 80
        print 'START'
        print '-' * 80
        print
            
        runner = unittest.TextTestRunner(sys.stdout)
        runner.run(suite)
            
        print
        print '-' * 80
        print 'FINISH'
        print '=' * 80
        return

    def listTests(self, suite):
        "List the available test cases."
        self.statusMessage('Available tests', 2)
        for test in suite._tests:
            print '%40s : %s' % (test.name, (test._TestCase__testMethod.__doc__ or ''))
        return

    def main(self, *args):
        "Run the required test suites."

        if args:
            raise ValueError('Unhandled arguments!', args)

        self.buildTests()

        actual_test_suite = unittest.TestSuite()

        if 'all' in self._desired_tests:
            actual_test_suite.addTest(self._suites)
        else:
            for test_name in self._desired_tests:
                try:
                    test_suite = self._suites.byName[test_name]
                except KeyError:
                    self.errorMessage('Unrecognized test case "%s" enabled.' % \
                                      test_name)
                    self.errorMessage('Skipping.')
                else:
                    actual_test_suite.addTest(test_suite)

        if self._operation == self.RUNTEST:
            self.runTests(actual_test_suite)
        elif self._operation == self.LIST:
            self.listTests(self._suites)
        else:
            raise ValueError('Operation (%s) must be one of RUNTEST or LIST.' % \
                             self._operation)
        return


def main(argv=()):
    try:
        TestCaseDriver(argv).run()
    except TestCaseDriver.HelpRequested:
        pass


def debug():
    main( ('-t', 'checkHelpSyntax') )
    return
    

if __name__ == '__main__':
    main(sys.argv[1:])
