1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4from __future__ import print_function, unicode_literals
5
6import sys
7import os
8import re
9
10from subprocess import Popen, PIPE
11
12import types
13if sys.version_info.major == 2:
14    import copy_reg as copyreg
15else:
16    import copyreg
17
18def _pickle_method(method):
19    func_name = method.im_func.__name__
20    obj = method.im_self
21    cls = method.im_class
22    return _unpickle_method, (func_name, obj, cls)
23
24def _unpickle_method(func_name, obj, cls):
25    for cls in cls.mro():
26        try:
27            func = cls.__dict__[func_name]
28        except KeyError:
29            pass
30        else:
31            break
32    return func.__get__(obj, cls)
33
34copyreg.pickle(types.MethodType, _pickle_method, _unpickle_method)
35
36class LedgerHarness:
37    ledger     = None
38    sourcepath = None
39    succeeded  = 0
40    failed     = 0
41    verify     = False
42    gmalloc    = False
43    python     = False
44
45    def __init__(self, argv):
46        if not os.path.isfile(argv[1]):
47            print("Cannot find ledger at '%s'" % argv[1])
48            sys.exit(1)
49        if not os.path.isdir(argv[2]):
50            print("Cannot find source path at '%s'" % argv[2])
51            sys.exit(1)
52
53        self.ledger     = os.path.abspath(argv[1])
54        self.sourcepath = os.path.abspath(argv[2])
55        self.succeeded  = 0
56        self.failed     = 0
57        self.verify     = '--verify' in argv
58        self.gmalloc    = '--gmalloc' in argv
59        self.python     = '--python' in argv
60
61    def run(self, command, verify=None, gmalloc=None, columns=True):
62        env = os.environ.copy()
63
64        if (gmalloc is not None and gmalloc) or \
65           (gmalloc is None and self.gmalloc):
66            env['MallocGuardEdges']      = '1'
67            env['MallocScribble']        = '1'
68            env['MallocPreScribble']     = '1'
69            env['MallocCheckHeapStart']  = '1000'
70            env['MallocCheckHeapEach']   = '10000'
71            env['DYLD_INSERT_LIBRARIES'] = '/usr/lib/libgmalloc.dylib'
72            env['MALLOC_PROTECT_BEFORE'] = '1'
73            env['MALLOC_FILL_SPACE']     = '1'
74            env['MALLOC_STRICT_SIZE']    = '1'
75
76        if (verify is not None and verify) or \
77           (verify is None and self.verify):
78            insert = ' --verify'
79        else:
80            insert = ''
81
82        if columns:
83            insert += ' --columns=80'
84
85        command = re.sub('\$ledger', '%s%s %s' % \
86                         (self.ledger, insert, '--args-only'), command)
87
88        valgrind = '/usr/bin/valgrind'
89        if not os.path.isfile(valgrind):
90            valgrind = '/opt/local/bin/valgrind'
91
92        if os.path.isfile(valgrind) and '--verify' in insert:
93            command = valgrind + ' -q ' + command
94
95        # If we are running under msys2, use bash to execute the test commands
96        if 'MSYSTEM' in os.environ:
97            bash_path = os.environ['MINGW_PREFIX'] + '/../usr/bin/bash.exe'
98            return Popen([bash_path, '-c', command], shell=False,
99                         close_fds=False, env=env, stdin=PIPE, stdout=PIPE,
100                         stderr=PIPE, cwd=self.sourcepath)
101
102        return Popen(command, shell=True, close_fds=True, env=env,
103                     stdin=PIPE, stdout=PIPE, stderr=PIPE,
104                     cwd=self.sourcepath)
105
106    def read(self, fd):
107        text = ""
108        text_data = os.read(fd.fileno(), 8192)
109        while text_data:
110            if text_data:
111                text += text_data
112            text_data = os.read(fd.fileno(), 8192)
113        return text
114
115    def readlines(self, fd):
116        lines = []
117        for line in fd.readlines():
118            if sys.version_info.major == 2:
119                line = unicode(line, 'utf-8')
120            else:
121                line = line.decode('utf-8')
122            if not line.startswith('GuardMalloc'):
123                lines.append(line)
124        return lines
125
126    def wait(self, process, msg='Ledger invocation failed:'):
127        if process.wait() != 0:
128            print(msg)
129            print(process.stderr.read())
130            self.failure()
131            return False
132        return True
133
134    def success(self):
135        sys.stdout.write(".")
136        sys.stdout.flush()
137        self.succeeded += 1
138
139    def failure(self, name=None):
140        sys.stdout.write("E")
141        if name:
142            sys.stdout.write("[%s]" % name)
143        sys.stdout.flush()
144        self.failed += 1
145
146    def exit(self):
147        print()
148        if self.succeeded > 0:
149            print("OK (%d) " % self.succeeded,)
150        if self.failed > 0:
151            print("FAILED (%d)" % self.failed,)
152        print()
153
154        sys.exit(self.failed)
155
156if __name__ == '__main__':
157    harness = LedgerHarness(sys.argv)
158    proc = harness.run('$ledger -f doc/sample.dat reg')
159    print('STDOUT:')
160    print(proc.stdout.read())
161    print('STDERR:')
162    print(proc.stderr.read())
163    harness.success()
164    harness.exit()
165