1#!/usr/bin/env python
2
3import os
4import sys
5
6import breakdancer
7from breakdancer import Condition, Effect, Action, Driver
8
9TESTKEY = 'testkey'
10
11######################################################################
12# Conditions
13######################################################################
14
15class ExistsCondition(Condition):
16
17    def __call__(self, state):
18        return TESTKEY in state
19
20class ExistsAsNumber(Condition):
21
22    def __call__(self, state):
23        try:
24            int(state[TESTKEY])
25            return True
26        except:
27            return False
28
29class MaybeExistsAsNumber(ExistsAsNumber):
30
31    def __call__(self, state):
32        return TESTKEY not in state or ExistsAsNumber.__call__(self, state)
33
34class DoesNotExistCondition(Condition):
35
36    def __call__(self, state):
37        return TESTKEY not in state
38
39class NothingExistsCondition(Condition):
40
41    def __call__(self, state):
42        return not bool(state)
43
44######################################################################
45# Effects
46######################################################################
47
48class StoreEffect(Effect):
49
50    def __init__(self, v='0'):
51        self.v = v
52
53    def __call__(self, state):
54        state[TESTKEY] = self.v
55
56class DeleteEffect(Effect):
57
58    def __call__(self, state):
59        del state[TESTKEY]
60
61class FlushEffect(Effect):
62
63    def __call__(self, state):
64        state.clear()
65
66class AppendEffect(Effect):
67
68    suffix = '-suffix'
69
70    def __call__(self, state):
71        state[TESTKEY] = state[TESTKEY] + self.suffix
72
73class PrependEffect(Effect):
74
75    prefix = 'prefix-'
76
77    def __call__(self, state):
78        state[TESTKEY] = self.prefix + state[TESTKEY]
79
80class ArithmeticEffect(Effect):
81
82    default = '0'
83
84    def __init__(self, by=1):
85        self.by = by
86
87    def __call__(self, state):
88        if TESTKEY in state:
89            state[TESTKEY] = str(max(0, int(state[TESTKEY]) + self.by))
90        else:
91            state[TESTKEY] = self.default
92
93######################################################################
94# Actions
95######################################################################
96
97class Set(Action):
98
99    effect = StoreEffect()
100    postconditions = [ExistsCondition()]
101
102class Add(Action):
103
104    preconditions = [DoesNotExistCondition()]
105    effect = StoreEffect()
106    postconditions = [ExistsCondition()]
107
108class Delete(Action):
109
110    preconditions = [ExistsCondition()]
111    effect = DeleteEffect()
112    postconditions = [DoesNotExistCondition()]
113
114class Flush(Action):
115
116    effect = FlushEffect()
117    postconditions = [NothingExistsCondition()]
118
119class Delay(Flush):
120    pass
121
122class Append(Action):
123
124    preconditions = [ExistsCondition()]
125    effect = AppendEffect()
126    postconditions = [ExistsCondition()]
127
128class Prepend(Action):
129
130    preconditions = [ExistsCondition()]
131    effect = PrependEffect()
132    postconditions = [ExistsCondition()]
133
134class Incr(Action):
135
136    preconditions = [ExistsAsNumber()]
137    effect = ArithmeticEffect(1)
138    postconditions = [ExistsAsNumber()]
139
140class Decr(Action):
141
142    preconditions = [ExistsAsNumber()]
143    effect = ArithmeticEffect(-1)
144    postconditions = [ExistsAsNumber()]
145
146class IncrWithDefault(Action):
147
148    preconditions = [MaybeExistsAsNumber()]
149    effect = ArithmeticEffect(1)
150    postconditions = [ExistsAsNumber()]
151
152class DecrWithDefault(Action):
153
154    preconditions = [MaybeExistsAsNumber()]
155    effect = ArithmeticEffect(-1)
156    postconditions = [ExistsAsNumber()]
157
158######################################################################
159# Driver
160######################################################################
161
162class TestFile(object):
163
164    def __init__(self, path, n=10):
165        self.tmpfilenames = ["%s_%d.c.tmp" % (path, i) for i in range(n)]
166        self.files = [open(tfn, "w") for tfn in self.tmpfilenames]
167        self.seq = [list() for f in self.files]
168        self.index = 0
169
170    def finish(self):
171        for f in self.files:
172            f.close()
173
174        for tfn in self.tmpfilenames:
175            nfn = tfn[:-4]
176            assert (nfn + '.tmp') == tfn
177            if os.path.exists(nfn):
178                os.remove(nfn)
179            os.rename(tfn, nfn)
180
181    def nextfile(self):
182        self.index += 1
183        if self.index >= len(self.files):
184            self.index = 0
185
186    def write(self, s):
187        self.files[self.index].write(s)
188
189    def addseq(self, seq):
190        self.seq[self.index].append(seq)
191
192class EngineTestAppDriver(Driver):
193
194    def __init__(self, writer=sys.stdout):
195        self.writer = writer
196
197    def output(self, s):
198        self.writer.write(s)
199
200    def preSuite(self, seq):
201        files = [self.writer]
202        if isinstance(self.writer, TestFile):
203            files = self.writer.files
204        for f in files:
205            f.write('/* DO NOT EDIT.. GENERATED SOURCE */\n\n')
206            f.write('#include "testsuite/breakdancer/disable_optimize.h"\n')
207            f.write('#include "testsuite/breakdancer/suite_stubs.h"\n\n')
208
209    def testName(self, seq):
210        return 'test_' + '_'.join(a.name for a in seq)
211
212    def startSequence(self, seq):
213        if isinstance(self.writer, TestFile):
214            self.writer.nextfile()
215            self.writer.addseq(seq)
216
217        f = "static enum test_result %s" % self.testName(seq)
218        self.output(("%s(ENGINE_HANDLE *h,\n%sENGINE_HANDLE_V1 *h1) {\n"
219                     % (f, " " * (len(f) + 1))))
220
221
222    def startAction(self, action):
223        if isinstance(action, Delay):
224            s = "    delay(expiry+1);"
225        elif isinstance(action, Flush):
226            s = "    flush(h, h1);"
227        elif isinstance(action, Delete):
228            s = '    del(h, h1);'
229        else:
230            s = '    %s(h, h1);' % (action.name)
231        self.output(s + "\n")
232
233    def _writeList(self, writer, fname, seq):
234        writer.write("""engine_test_t* %s(void) {
235    static engine_test_t tests[]  = {
236
237""" % fname)
238        for seq in sorted(seq):
239            writer.write('        {"%s",\n         %s,\n         test_setup, teardown, NULL},\n' % (
240                    ', '.join(a.name for a in seq),
241                    self.testName(seq)))
242
243        writer.write("""        {NULL, NULL, NULL, NULL, NULL, NULL, NULL}
244    };
245    return tests;
246}
247""")
248
249    def postSuite(self, seq):
250        if isinstance(self.writer, TestFile):
251            for i, v in enumerate(self.writer.files):
252                self._writeList(v, 'get_tests_%d' % i,
253                                self.writer.seq[i])
254        else:
255            self._writeList(self.writer, 'get_tests', seq)
256
257    def endSequence(self, seq, state):
258        val = state.get(TESTKEY)
259        if val:
260            self.output('    checkValue(h, h1, "%s");\n' % val)
261        else:
262            self.output('    assertNotExists(h, h1);\n')
263        self.output("    return SUCCESS;\n")
264        self.output("}\n\n")
265
266    def endAction(self, action, state, errored):
267        value = state.get(TESTKEY)
268        if value:
269            vs = ' /* value is "%s" */\n' % value
270        else:
271            vs = ' /* value is not defined */\n'
272
273        if errored:
274            self.output("    assertHasError();" + vs)
275        else:
276            self.output("    assertHasNoError();" + vs)
277
278if __name__ == '__main__':
279    w = TestFile('generated_suite')
280    breakdancer.runTest(breakdancer.findActions(globals().values()),
281                        EngineTestAppDriver(w))
282    w.finish()
283