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        #compiler='clang'
125        compiler='gcc'
126        commandline='%s %s %s 2>&1' % (compiler, compileOptions, self.filename)
127#        print
128#        print "%s (%s)" % (self.filename, compiler)
129#        print
130        fp = os.popen(commandline)
131        data = fp.read()
132        xit = fp.close()
133        if xit != None:
134            self.fail("Compile failed[%s]:\n%s"%(xit, data))
135
136
137    def runTestCase(self):
138        os.environ['DYLD_BIND_AT_LAUNCH'] = '1'
139        fp = os.popen('/tmp/test.bin', 'r')
140        del os.environ['DYLD_BIND_AT_LAUNCH']
141        data = fp.read()
142        xit = fp.close()
143        if xit != None:
144            self.fail("Running failed (%s)"%(exitCode2Description(xit),))
145        return data
146
147
148    def object_files(self, basedir):
149        result = []
150        for dirpath, dirnames, filenames in os.walk(basedir):
151            for fn in filenames:
152                if fn.endswith('.o'):
153                    result.append(os.path.join(dirpath, fn))
154        return result
155
156
157def testSuiteForDirectory(dirname):
158    tests = []
159    for fn in os.listdir(dirname):
160        if not fn.endswith('.c'): continue
161        tst = DgTestCase(os.path.join(dirname, fn))
162        if alltests and tst.shortDescription() not in alltests:
163            continue
164        tests.append(tst)
165
166    return unittest.TestSuite(tests)
167
168alltests = []
169if __name__ == "__main__":
170    alltests = sys.argv[1:]
171    runner = unittest.TextTestRunner(verbosity=5)
172    runner.run(testSuiteForDirectory('tests/testsuite/libffi.call'))
173