1#!/usr/bin/python
2"""
3A very crude emulator of dejagnu, just enough to integrate the libbfi
4unittests into the pyobjc ones.
5"""
6import os
7import re
8import sys
9import signal
10from fnmatch import fnmatch
11import unittest
12from distutils.util import get_platform
13
14gDgCommands=re.compile(r'''
15        (?:{\s*(dg-do)\s*run\s*({[^}]*})?\s*})
16        |
17        (?:{\s*(dg-output)\s*"([^"]*)"\s*})
18        ''',
19            re.VERBOSE|re.MULTILINE)
20
21def signame(code):
22    for nm in dir(signal):
23        if nm.startswith('SIG') and nm[3] != '_' \
24                and getattr(signal, nm) == code:
25            return nm
26    return code
27
28def exitCode2Description(code):
29    """
30    Convert the exit code as returned by os.popen().close() to a string
31    """
32    if os.WIFEXITED(code):
33        return 'exited with status %s'%(os.WEXITSTATUS(code),)
34
35    elif os.WIFSIGNALED(code):
36        sig = os.WTERMSIG(code)
37        return 'crashed with signal %s [%s]'%(signame(sig), sig)
38
39    else:
40        return 'exit code %s'%(code,)
41
42def platform_matches(matchstr):
43    # This is a hack
44    if sys.byteorder == 'little':
45        platform = 'i386-apple-darwin'
46    else:
47        platform = 'powerpc-apple-darwin'
48
49    return fnmatch(platform, matchstr)
50
51def parseDG(fdata):
52    result = []
53    for  item in gDgCommands.findall(fdata):
54        if item[0] == 'dg-do':
55            result.append(('run', item[1]))
56        elif item[2] == 'dg-output':
57            result.append(('expect', item[3].decode('string_escape')))
58    return result
59
60
61class DgTestCase (unittest.TestCase):
62    def __init__(self, filename):
63        unittest.TestCase.__init__(self)
64        self.filename = filename
65
66    #archOption = "-arch ppc"
67    #archOption = "-arch ppc64"
68    #archOption = "-arch i386"
69    archOption = "-arch x86_64"
70    #archOption = ""
71    compileOptionsBase = "-g -DMACOSX -Iinclude -o /tmp/test.bin -lffi"
72    compileOptionsList = ( # HACK ALERT: Yes, there are better ways to do this, but this is easy and extremely flexible
73        "%s %s %s" % (compileOptionsBase, archOption, "-O0"),
74        "%s %s %s" % (compileOptionsBase, archOption, "-O1"),
75        "%s %s %s" % (compileOptionsBase, archOption, "-O2"),
76        "%s %s %s" % (compileOptionsBase, archOption, "-O3"),
77        "%s %s %s" % (compileOptionsBase, archOption, "-Os"),
78        "%s %s %s" % (compileOptionsBase, archOption, "-Oz"),  # Note: Apple-Only, see gcc man page for details
79        )
80    def runTest(self):
81        script = parseDG(open(self.filename).read())
82        output = []
83
84        for command, data in script:
85            if command == 'run':
86                action = 'run'
87                action_data = data
88            if command == 'expect':
89                output.append(data)
90        output = ''.join(output)
91        output = output.replace('\\', '')
92
93        d = action_data.split()
94        if d and d[1] == 'target':
95            for item in d[2:]:
96                if platform_matches(item):
97                    break
98
99            else:
100                # Test shouldn't be run on this platform
101                return
102
103        # NOTE: We're ignoring the xfail data for now, none of the
104        # testcases are supposed to fail on darwin.
105
106        for compileOptions in self.compileOptionsList:
107            self.compileTestCase(compileOptions)
108            data = self.runTestCase()
109
110            if output != '':
111                self.assertEquals(data.rstrip(), output.rstrip())
112                os.unlink('/tmp/test.bin')
113
114
115    def shortDescription(self):
116        fn = os.path.basename(self.filename)[:-2]
117        dn = os.path.basename(os.path.dirname(self.filename))
118        return "dejagnu.%s.%s"%(dn, fn)
119
120    def compileTestCase(self, compileOptions):
121        # libdir = os.path.join('build', 'temp.%s-%d.%d'%(get_platform(), sys.version_info[0], sys.version_info[1]), 'libffi-src')
122        # libffiobjects = self.object_files(libdir)
123
124        commandline='cc %s %s 2>&1' % (compileOptions, self.filename)
125        fp = os.popen(commandline)
126        data = fp.read()
127        xit = fp.close()
128        if xit != None:
129            self.fail("Compile failed[%s]:\n%s"%(xit, data))
130
131
132    def runTestCase(self):
133        os.environ['DYLD_BIND_AT_LAUNCH'] = '1'
134        fp = os.popen('/tmp/test.bin', 'r')
135        del os.environ['DYLD_BIND_AT_LAUNCH']
136        data = fp.read()
137        xit = fp.close()
138        if xit != None:
139            self.fail("Running failed (%s)"%(exitCode2Description(xit),))
140        return data
141
142
143    def object_files(self, basedir):
144        result = []
145        for dirpath, dirnames, filenames in os.walk(basedir):
146            for fn in filenames:
147                if fn.endswith('.o'):
148                    result.append(os.path.join(dirpath, fn))
149        return result
150
151
152def testSuiteForDirectory(dirname):
153    tests = []
154    for fn in os.listdir(dirname):
155        if not fn.endswith('.c'): continue
156        tst = DgTestCase(os.path.join(dirname, fn))
157        if alltests and tst.shortDescription() not in alltests:
158            continue
159        tests.append(tst)
160
161    return unittest.TestSuite(tests)
162
163alltests = []
164if __name__ == "__main__":
165    alltests = sys.argv[1:]
166    runner = unittest.TextTestRunner(verbosity=2)
167    runner.run(testSuiteForDirectory('tests/testsuite/libffi.call'))
168