1#!/usr/bin/env python
2
3import breakdancer
4from breakdancer import Condition, Effect, Action, Driver
5
6TESTKEY = 'testkey'
7
8######################################################################
9# Conditions
10######################################################################
11
12class ExistsCondition(Condition):
13
14    def __call__(self, state):
15        return TESTKEY in state
16
17class ExistsAsNumber(Condition):
18
19    def __call__(self, state):
20        try:
21            int(state[TESTKEY])
22            return True
23        except:
24            return False
25
26class MaybeExistsAsNumber(ExistsAsNumber):
27
28    def __call__(self, state):
29        return TESTKEY not in state or ExistsAsNumber.__call__(self, state)
30
31class DoesNotExistCondition(Condition):
32
33    def __call__(self, state):
34        return TESTKEY not in state
35
36class NothingExistsCondition(Condition):
37
38    def __call__(self, state):
39        return not bool(state)
40
41######################################################################
42# Effects
43######################################################################
44
45class StoreEffect(Effect):
46
47    def __init__(self, v='0'):
48        self.v = v
49
50    def __call__(self, state):
51        state[TESTKEY] = self.v
52
53class DeleteEffect(Effect):
54
55    def __call__(self, state):
56        del state[TESTKEY]
57
58class FlushEffect(Effect):
59
60    def __call__(self, state):
61        state.clear()
62
63class AppendEffect(Effect):
64
65    suffix = '-suffix'
66
67    def __call__(self, state):
68        state[TESTKEY] = state[TESTKEY] + self.suffix
69
70class PrependEffect(Effect):
71
72    prefix = 'prefix-'
73
74    def __call__(self, state):
75        state[TESTKEY] = self.prefix + state[TESTKEY]
76
77class ArithmeticEffect(Effect):
78
79    default = '0'
80
81    def __init__(self, by=1):
82        self.by = by
83
84    def __call__(self, state):
85        if TESTKEY in state:
86            state[TESTKEY] = str(max(0, int(state[TESTKEY]) + self.by))
87        else:
88            state[TESTKEY] = self.default
89
90######################################################################
91# Actions
92######################################################################
93
94class Set(Action):
95
96    effect = StoreEffect()
97    postconditions = [ExistsCondition()]
98
99class Add(Action):
100
101    preconditions = [DoesNotExistCondition()]
102    effect = StoreEffect()
103    postconditions = [ExistsCondition()]
104
105class Delete(Action):
106
107    preconditions = [ExistsCondition()]
108    effect = DeleteEffect()
109    postconditions = [DoesNotExistCondition()]
110
111class Flush(Action):
112
113    effect = FlushEffect()
114    postconditions = [NothingExistsCondition()]
115
116class Delay(Flush):
117    pass
118
119class Append(Action):
120
121    preconditions = [ExistsCondition()]
122    effect = AppendEffect()
123    postconditions = [ExistsCondition()]
124
125class Prepend(Action):
126
127    preconditions = [ExistsCondition()]
128    effect = PrependEffect()
129    postconditions = [ExistsCondition()]
130
131class Incr(Action):
132
133    preconditions = [ExistsAsNumber()]
134    effect = ArithmeticEffect(1)
135    postconditions = [ExistsAsNumber()]
136
137class Decr(Action):
138
139    preconditions = [ExistsAsNumber()]
140    effect = ArithmeticEffect(-1)
141    postconditions = [ExistsAsNumber()]
142
143class IncrWithDefault(Action):
144
145    preconditions = [MaybeExistsAsNumber()]
146    effect = ArithmeticEffect(1)
147    postconditions = [ExistsAsNumber()]
148
149class DecrWithDefault(Action):
150
151    preconditions = [MaybeExistsAsNumber()]
152    effect = ArithmeticEffect(-1)
153    postconditions = [ExistsAsNumber()]
154
155######################################################################
156# Driver
157######################################################################
158
159class EngineTestAppDriver(Driver):
160
161    def preSuite(self, seq):
162        print '#include "suite_stubs.h"'
163        print ""
164
165    def testName(self, seq):
166        return 'test_' + '_'.join(a.name for a in seq)
167
168    def startSequence(self, seq):
169        f = "static enum test_result %s" % self.testName(seq)
170        print ("%s(ENGINE_HANDLE *h,\n%sENGINE_HANDLE_V1 *h1) {"
171               % (f, " " * (len(f) + 1)))
172
173    def startAction(self, action):
174        if isinstance(action, Delay):
175            s = "    delay(expiry+1);"
176        elif isinstance(action, Flush):
177            s = "    flush(h, h1);"
178        elif isinstance(action, Delete):
179            s = '    del(h, h1);'
180        else:
181            s = '    %s(h, h1);' % (action.name)
182        print s
183
184    def postSuite(self, seq):
185        print """MEMCACHED_PUBLIC_API
186engine_test_t* get_tests(void) {
187
188    static engine_test_t tests[]  = {
189"""
190        for seq in sorted(seq):
191            print '        {"%s",\n         %s,\n         test_setup, teardown, NULL},' % (
192                ', '.join(a.name for a in seq),
193                self.testName(seq))
194
195        print """        {NULL, NULL, NULL, NULL, NULL}
196    };
197    return tests;
198}"""
199
200    def endSequence(self, seq, state):
201        val = state.get(TESTKEY)
202        if val:
203            print '    checkValue(h, h1, "%s");' % val
204        else:
205            print '    assertNotExists(h, h1);'
206        print "    return SUCCESS;"
207        print "}"
208        print ""
209
210    def endAction(self, action, state, errored):
211        value = state.get(TESTKEY)
212        if value:
213            vs = ' // value is "%s"' % value
214        else:
215            vs = ' // value is not defined'
216
217        if errored:
218            print "    assertHasError();" + vs
219        else:
220            print "    assertHasNoError();" + vs
221
222if __name__ == '__main__':
223    breakdancer.runTest(breakdancer.findActions(globals().values()),
224                        EngineTestAppDriver())
225