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