#
# $Id: test_happydoc.py,v 1.74 2001/12/16 12:13:53 doughellmann Exp $
#
# 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.74 2001/12/16 12:13:53 doughellmann Exp $',
    'creator'      : 'Doug Hellmann <doug@hellfly.net>',
    'project'      : 'HappyDoc',
    'created'      : 'Sun, 13-Aug-2000 10:16:13 EDT',

    #
    #  Current Information
    #
    'author'       : '$Author: doughellmann $',
    'version'      : '$Revision: 1.74 $',
    'date'         : '$Date: 2001/12/16 12:13:53 $',
}
try:
    __version__ = __rcs_info__['version'].split(' ')[1]
except:
    __version__ = '0.0'

#
# Import system modules
#
import sys
import os
import tempfile
import string
from glob import glob
import pprint
import unittest
    
#
# Local Modules
#
import happydoclib
from happydoclib.appclass import HappyDoc
from happydoclib.StreamFlushTest import StreamFlushTest, DEFAULT_OUTPUT_DIR
from happydoclib.StreamFlushTest import verboseLevel as globalVerboseLevel

#
# Modules with tests
#
import happydoclib.docset.docset_MultipleFile
import happydoclib.happydocset
import happydoclib.happydocstring
import happydoclib.docstring
import happydoclib.docstring.docstring_ClassicStructuredText
import happydoclib.docstring.docstring_StructuredText
import happydoclib.docstring.docstring_RawText
import happydoclib.docstring.docstring_PlainText
import happydoclib.formatter.openoffice
import happydoclib.parseinfo
import happydoclib.trace

#
# Module
#

    
    
class HappyDocTestBase(StreamFlushTest):

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

    def runHappyDoc(self, modules=(), extraArgs=()):
        #
        # Fix up output directory variable
        #
        output_dir = self.output_dir
        happydoc = self.happydoc
        test_name = self.name
        output_dir = '%(output_dir)s/%(test_name)s' % locals()
        #
        # Verbose level setting
        #
        if self.verboseLevel.get():
            verboseArgs = '-%s' % ('v' * self.verboseLevel.get())
        else:
            verboseArgs = '-q'
        #
        # 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', self.name,
                 '-d', output_dir,
                 '-i', 'TestCases',
                 '-i', 'tmp',
                 ] + \
                 \
                 list(extraArgs) + \
                 \
                 [ 'formatter_dateStampFiles=0',  # always different, breaks diff
                   ] + \
                   \
                   list(modules)

        #
        # Set up for the test.
        #
        
        try:
            
            if self.verboseLevel.get():
                print
                print
                print 'Arguments: ',
                pprint.pprint(argv)
                print
                
            exit_code = HappyDoc(argv).run()
            
        except HappyDoc.HelpRequested, msg:
            if msg:
                exit_code = 1
        
        #
        # Flush the output so watchers can keep up
        #
        sys.stdout.flush()
        sys.stderr.flush()
        return exit_code
    
class ExecuteHappyDocTest(HappyDocTestBase):

    """Tests involving external executions of HappyDoc.

    Separated the definitions of the test from the base class because
    unittest appears to duplicate tests if they are inherited.
    """

    #
    # Application tests
    #
    
    def testHelpSyntax(self):
        assert self.runHappyDoc( (),
                                 extraArgs=('-h',)
                                 ), 'Basic help syntax test failed.'
        return
 
    def testHelpManual(self):
        assert self.runHappyDoc( (),
                                 extraArgs=('--help',)
                                 ), 'Help manual generation test failed.'
        return
 
    def testUsageWhenNoArgs(self):
        assert self.runHappyDoc( (),
                                 extraArgs=()
                                 ), 'Usage message test failed.'
        return

    #
    # Formatter and docset tests
    #
    
    def testHTMLSingleFile(self):
        assert (not self.runHappyDoc( ('TestCases/test.py',),
                                      extraArgs=('-p', '-', '-T', 'SingleFile')
                                      )
                ), 'Basic single-file docset test failed.'
        return
    
    def testHTMLSingleFileCompact(self):
        assert (not self.runHappyDoc( ('TestCases/test.py',),
                                      extraArgs=('-p', '-', '-T', 'SingleFile',
                                                 'formatter_compactHTML=yes',
                                                 )
                                      )
                ), 'Basic single-file docset test failed.'
        return

    def testHTMLStdout(self):
        assert (not self.runHappyDoc( ('TestCases/test.py',),
                                      extraArgs=('-T', 'StdOut')
                                      )
                ), 'HTML to standard-output docset test failed.'
        return

    def testText(self):
        assert (not self.runHappyDoc( ('TestCases/test.py',),
                                      extraArgs=('-F', 'Text')
                                      )
                ), 'Text formatter test failed.'
        return

    def testTextStdout(self):
        assert (not self.runHappyDoc( ('TestCases/test.py',),
                                      extraArgs=('-F', 'Text', '-T', 'StdOut')
                                      )
                ), 'Text to standard-output docset test failed.'
        return

    def testDia(self):
        assert (not self.runHappyDoc( ('TestCases/test_dia.py', ),
                                      extraArgs=('--dia',)
                                      )
                ), 'Dia output test failed.'
        return

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

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

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

    ##
    ## Full self documentation tests
    ##
    
#     def testSelfDia(self):
#         assert (not self.runHappyDoc( ('../HappyDoc',),
#                                       extraArgs=('--dia',))
#                 ), 'Full self documentation test in dia format failed.'
#         return
   
    def testSelfHTML(self):
        assert (not self.runHappyDoc( ('../HappyDoc',),
                                      )
                ), 'Full self documentation test failed.'
        return
    
#     def testSelfHTMLCompact(self):
#         assert (not self.runHappyDoc( ('../HappyDoc',),
#                                       extraArgs=('formatter_compactHTML=yes',))
#                 ), 'Full self documentation with compact output test failed.'
#         return
    
    def testSelfDocBookSingleFile(self):
        assert (not self.runHappyDoc( ('../HappyDoc',),
                                      extraArgs=('-F',
                                                 'SGMLDocBook',
                                                 '-T',
                                                 'SingleFile'))
                ), 'Full self documentation in DocBook format output test failed.'
        return

#     def testSelfDocBookXML(self):
#         assert (not self.runHappyDoc( ('../HappyDoc',),
#                                       extraArgs=('-F',
#                                                  'docbookx',
#                                                  '-T',
#                                                  'mstruct'))
#                 ), 'Full self documentation in DocBookX format output test failed.'
#         return 
    
#     def testSelfPlainText(self):
#         assert (not self.runHappyDoc( ('../HappyDoc',),
#                                       extraArgs=('-F', 'Text'))
#                 ), 'Full self documentation-as-text test failed.'
#         return
    

class OtherWorkingDirTest(HappyDocTestBase):

    def __init__(self,
                 workingDir='.',
                 outputDir='DefaultTestOutputDir',
                 **nargs
                 ):
        #
        # Base class
        #
        output_dir = happydoclib.path.join(os.pardir, 'HappyDoc', outputDir)
        nargs['outputDir'] = output_dir
        apply(HappyDocTestBase.__init__, (self,), nargs)
        #
        # This class
        #
        self.dir_stack = None
        self.working_dir = workingDir
        return

    def setUp(self):
        self.happydoc = '../HappyDoc/happydoc'
        return
    
    def runHappyDoc(self, *args, **nargs):
        self.pushDir()
        apply(HappyDocTestBase.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(HappyDocTestBase):

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

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


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

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

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

    def testZopeWithSafePrefix(self):
        assert (not self.runHappyDoc( ('../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(HappyDocTestBase):

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

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

    def __getattr__(self, name):
        prefix = 'testBugReport'
        prefix_len = len(prefix)
        if name[:prefix_len] == prefix:
            id = name[prefix_len:]
            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)


class HappyDocTestLoader(unittest.TestLoader):
    """Special TestLoader for HappyDoc

    This TestLoader subclass tell the TestCases it loads to write to a
    specific output directory, thereby letting us differentiate
    between standard test output and regression test output.
    """

    def __init__(self, outputDir):
        self.output_dir = outputDir
        return

    def loadTestsFromTestCase(self, testCaseClass):
        """Return a suite of all tests cases contained in testCaseClass"""
        names = self.getTestCaseNames(testCaseClass)
        tests = []
        for n in names:
            tests.append( testCaseClass(n, self.output_dir) )
        return self.suiteClass(tests)
        

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

    LIST = 'list'
    RUNTEST = 'run'

    _include_zope = 0
    _output_dir = 'DefaultTestOutputDir'
    _operation = RUNTEST

    #
    # Use the verbosity manager from the StreamFlushTest
    # module so that all verbosity is managed the same way.
    #
    verboseLevel = globalVerboseLevel

    ##
    ## OPTION HANDLERS
    ##

    def optionHandler_d(self, outputDir):
        "Specify the output directory for the tests."
        self.statusMessage('Setting output directory to "%s"' % outputDir, 2)
        self._output_dir = outputDir
        return

    def optionHandler_list(self):
        "List the tests available."
        self._operation = self.LIST
        return
    
    def optionHandler_q(self):
        "Disable visible output."
        self.verboseLevel.unset()
        return

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

    def optionHandler_withzope(self):
        "Add the Zope tests to the set."
        self.statusMessage('Including Zope testes', 2)
        self._include_zope = 1
        return


    ##
    ## APPLICATION
    ##
    
    def appInit(self):
        self._desired_tests = []
        self.optionHandler_d(DEFAULT_OUTPUT_DIR)
        return

    def listTests(self, suite):
        "List the available test cases."
        self.statusMessage('Available tests', 2)
        for test in suite._tests:
            if issubclass(test.__class__, unittest.TestSuite):
                self.listTests(test)
            else:
                try:
                    description = test.shortDescription()
                    if not description:
                        description = test.id()
                    print '%s : %s' % (test.name, description)
                except AttributeError:
                    print dir(test)
                    raise
        return

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

        verbosity = self.verboseLevel.get()

        if verbosity > 1:
            print '=' * 80
            print 'START'
            print '-' * 80
            print
            
        runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=verbosity)
        runner.run(suite)

        if verbosity > 1:
            print
            print '-' * 80
            print 'FINISH'
            print '=' * 80
            
        return
    
    def main(self, *args):
        "Run the requested test suites."

        #
        # Create the output directory
        #
        self.statusMessage('Creating %s' % self._output_dir, 2)
        happydoclib.path.rmkdir(self._output_dir)
        
        #
        # Figure out what tests to process
        #
        actual_test_suite = unittest.TestSuite()

        test_loader = HappyDocTestLoader(self._output_dir)

        get_all_tests = 0
        if 'all' in args:
            # We will define "all" of the tests later
            get_all_tests = 1
        elif args:
            # Run the tests specified by the user
            for a in args:
                actual_test_suite.addTest( test_loader.loadTestsFromName(a) )
        else:
            # Default to running all tests
            get_all_tests = 0
            actual_test_suite.addTest( test_loader.loadTestsFromName('__main__.ExecuteHappyDocTest.testSelfHTML') )

        if get_all_tests:
            #
            # Load tests from modules we know contain tests
            #
            for m in (
                #
                # Supporting tools
                #
                happydoclib.CommandLineApp,
                happydoclib.indentstring,
                happydoclib.optiontools,
                happydoclib.trace,
                happydoclib.path,

                #
                # Foundation modules
                #
                happydoclib.parsecomments,
                happydoclib.parseinfo,
                happydoclib.happydocstring,
                happydoclib.happydocset,

                #
                # Docstring converters
                #
                happydoclib.docstring,
                happydoclib.docstring.docstring_ClassicStructuredText,
                happydoclib.docstring.docstring_StructuredText,
                happydoclib.docstring.docstring_RawText,
                happydoclib.docstring.docstring_PlainText,

                #
                # Formatters
                #
                happydoclib.formatter.openoffice,
                happydoclib.formatter.formatter_HTMLFile,

                #
                # Docsets
                #
                happydoclib.docset.docset_MultipleFile
                
                ):
                actual_test_suite.addTest(test_loader.loadTestsFromModule(m))
            #
            # Load tests from classes in this module
            #
            for c in ( ExecuteHappyDocTest,
                       ExternalTest,
                       ):
                actual_test_suite.addTest(test_loader.loadTestsFromTestCase(c))
            #
            # 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) )
                actual_test_suite.addTest(
                    HappyDocBugRegressionTest( methodName=bug,
                                               outputDir=self._output_dir,
                                               )
                    )
            #
            # Optionally include the Zope tests
            #
            if self._include_zope:
                actual_test_suite.addTest(test_loader.loadTestsFromTestCase(ZopeTest))
                
        #
        # Figure out what action to take
        #
        if self._operation == self.RUNTEST:
            self.runTests(actual_test_suite)
        elif self._operation == self.LIST:
            self.listTests(actual_test_suite)
        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:])
