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        script = parseDG(open(self.filename).read())
74        output = []
75
76        for command, data in script:
77            if command == 'run':
78                action = 'run'
79                action_data = data
80            if command == 'expect':
81                output.append(data)
82        output = ''.join(output)
83        output = output.replace('\\', '')
84
85        d = action_data.split()
86        if d and d[1] == 'target':
87            for item in d[2:]:
88                if platform_matches(item):
89                    break
90
91            else:
92                # Test shouldn't be run on this platform
93                return
94
95        # NOTE: We're ignoring the xfail data for now, none of the
96        # testcases are supposed to fail on darwin.
97
98        self.compileTestCase()
99        data = self.runTestCase()
100
101        if output != '':
102            self.assertEquals(data.rstrip(), output.rstrip())
103        os.unlink('/tmp/test.bin')
104
105
106    def shortDescription(self):
107        fn = os.path.basename(self.filename)[:-2]
108        dn = os.path.basename(os.path.dirname(self.filename))
109        return "dejagnu.%s.%s"%(dn, fn)
110
111    def compileTestCase(self):
112        libdir = os.path.join('build', 'temp.%s-%d.%d'%(get_platform(), sys.version_info[0], sys.version_info[1]))
113        if hasattr(sys, 'gettotalrefcount'):
114            libdir += "-pydebug"
115        libdir = os.path.join(libdir, 'libffi-src')
116
117        libffiobjects = self.object_files(libdir)
118
119
120        if self.filename.endswith('.m'):
121            extra_link = '-framework Foundation'
122        else:
123            extra_link = ''
124
125        commandline='MACOSX_DEPLPOYMENT_TARGET=%s %s %s -g -DMACOSX -Ilibffi-src/include -Ilibffi-src/powerpc -o /tmp/test.bin %s %s %s 2>&1'%(
126                get_config_var('MACOSX_DEPLOYMENT_TARGET'),
127                get_config_var('CC'),
128                get_config_var('CFLAGS'), self.filename, ' '.join(libffiobjects),
129		extra_link)
130
131        fp = os.popen(commandline)
132        data = fp.read()
133        xit = fp.close()
134        if xit != None:
135            self.fail("Compile failed[%s]:\n%s"%(xit, data))
136
137
138    def runTestCase(self):
139        os.environ['DYLD_BIND_AT_LAUNCH'] = '1'
140        fp = os.popen('/tmp/test.bin', 'r')
141        del os.environ['DYLD_BIND_AT_LAUNCH']
142        data = fp.read()
143        xit = fp.close()
144        if xit != None:
145            self.fail("Running failed (%s)"%(exitCode2Description(xit),))
146        return data
147
148
149    def object_files(self, basedir):
150        result = []
151        for dirpath, dirnames, filenames in os.walk(basedir):
152            for fn in filenames:
153                if fn.endswith('.o'):
154                    result.append(os.path.join(dirpath, fn))
155        return result
156
157
158def testSuiteForDirectory(dirname):
159    tests = []
160    for fn in os.listdir(dirname):
161        if not fn.endswith('.c') and not fn.endswith('.m'): continue
162        tst = DgTestCase(os.path.join(dirname, fn))
163        if alltests and tst.shortDescription() not in alltests:
164            continue
165        tests.append(tst)
166
167    return unittest.TestSuite(tests)
168
169
170alltests = []
171if __name__ == "__main__":
172    alltests = sys.argv[1:]
173    runner = unittest.TextTestRunner(verbosity=2)
174    runner.run(testSuiteForDirectory('libffi-src/tests/testsuite/libffi.call'))
175