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 44class IsLockedCondition(Condition): 45 46 def __call__(self, state): 47 return (TESTKEY + '.lock') in state 48 49class IsUnlockedCondition(Condition): 50 51 def __call__(self, state): 52 return (TESTKEY + '.lock') not in state 53 54class KnowsCAS(Condition): 55 56 def __call__(self, state): 57 return (TESTKEY + '.cas') in state 58 59###################################################################### 60# Effects 61###################################################################### 62 63class NoFX(Effect): 64 65 def __call__(self, state): 66 pass 67 68class MutationEffect(Effect): 69 70 def forgetCAS(self, state): 71 k = TESTKEY + '.cas' 72 if k in state: 73 del state[k] 74 75class StoreEffect(MutationEffect): 76 77 def __init__(self, rememberCAS=False): 78 self.rememberCAS = rememberCAS 79 80 def __call__(self, state): 81 state[TESTKEY] = '0' 82 if self.rememberCAS: 83 state[TESTKEY + '.cas'] = 1 84 else: 85 self.forgetCAS(state) 86 87class LockEffect(MutationEffect): 88 89 def __call__(self, state): 90 state[TESTKEY + '.lock'] = True 91 self.forgetCAS(state) 92 93class UnlockEffect(Effect): 94 95 def __call__(self, state): 96 klock = TESTKEY + '.lock' 97 if klock in state: 98 del state[klock] 99 100class DeleteEffect(MutationEffect): 101 102 def __call__(self, state): 103 del state[TESTKEY] 104 klock = TESTKEY + '.lock' 105 if klock in state: 106 del state[klock] 107 self.forgetCAS(state) 108 109class FlushEffect(Effect): 110 111 def __call__(self, state): 112 state.clear() 113 114class AppendEffect(MutationEffect): 115 116 suffix = '-suffix' 117 118 def __call__(self, state): 119 state[TESTKEY] = state[TESTKEY] + self.suffix 120 self.forgetCAS(state) 121 122class PrependEffect(MutationEffect): 123 124 prefix = 'prefix-' 125 126 def __call__(self, state): 127 state[TESTKEY] = self.prefix + state[TESTKEY] 128 self.forgetCAS(state) 129 130class ArithmeticEffect(MutationEffect): 131 132 default = '0' 133 134 def __init__(self, by=1): 135 self.by = by 136 137 def __call__(self, state): 138 if TESTKEY in state: 139 state[TESTKEY] = str(max(0, int(state[TESTKEY]) + self.by)) 140 else: 141 state[TESTKEY] = self.default 142 self.forgetCAS(state) 143 144###################################################################### 145# Actions 146###################################################################### 147 148class Get(Action): 149 150 preconditions = [ExistsCondition()] 151 effect = NoFX() 152 153class Set(Action): 154 155 preconditions = [IsUnlockedCondition()] 156 effect = StoreEffect() 157 postconditions = [ExistsCondition()] 158 159class setUsingCAS(Action): 160 161 preconditions = [KnowsCAS(), IsUnlockedCondition()] 162 effect = StoreEffect() 163 postconditions = [ExistsCondition()] 164 165class SetRetainCAS(Action): 166 167 preconditions = [IsUnlockedCondition()] 168 effect = StoreEffect(True) 169 postconditions = [ExistsCondition(), KnowsCAS()] 170 171class Add(Action): 172 173 preconditions = [DoesNotExistCondition(), IsUnlockedCondition()] 174 effect = StoreEffect() 175 postconditions = [ExistsCondition()] 176 177class Delete(Action): 178 179 preconditions = [ExistsCondition(), IsUnlockedCondition()] 180 effect = DeleteEffect() 181 postconditions = [DoesNotExistCondition()] 182 183class DeleteUsingCAS(Action): 184 185 preconditions = [KnowsCAS(), ExistsCondition(), IsUnlockedCondition()] 186 effect = DeleteEffect() 187 postconditions = [DoesNotExistCondition()] 188 189class Flush(Action): 190 191 effect = FlushEffect() 192 postconditions = [NothingExistsCondition()] 193 194class WaitForLock(Action): 195 196 effect = UnlockEffect() 197 198class GetLock(Action): 199 200 preconditions = [ExistsCondition(), IsUnlockedCondition()] 201 effect = LockEffect() 202 postconditions = [IsLockedCondition()] 203 204class Delay(Flush): 205 pass 206 207class Append(Action): 208 209 preconditions = [ExistsCondition(), IsUnlockedCondition()] 210 effect = AppendEffect() 211 postconditions = [ExistsCondition()] 212 213class Prepend(Action): 214 215 preconditions = [ExistsCondition(), IsUnlockedCondition()] 216 effect = PrependEffect() 217 postconditions = [ExistsCondition()] 218 219class AppendUsingCAS(Action): 220 221 preconditions = [KnowsCAS(), ExistsCondition(), IsUnlockedCondition()] 222 effect = AppendEffect() 223 postconditions = [ExistsCondition()] 224 225class PrependUsingCAS(Action): 226 227 preconditions = [KnowsCAS(), ExistsCondition(), IsUnlockedCondition()] 228 effect = PrependEffect() 229 postconditions = [ExistsCondition()] 230 231class Incr(Action): 232 233 preconditions = [ExistsAsNumber(), IsUnlockedCondition()] 234 effect = ArithmeticEffect(1) 235 postconditions = [ExistsAsNumber()] 236 237class Decr(Action): 238 239 preconditions = [ExistsAsNumber(), IsUnlockedCondition()] 240 effect = ArithmeticEffect(-1) 241 postconditions = [ExistsAsNumber()] 242 243class IncrWithDefault(Action): 244 245 preconditions = [MaybeExistsAsNumber(), IsUnlockedCondition()] 246 effect = ArithmeticEffect(1) 247 postconditions = [ExistsAsNumber()] 248 249class DecrWithDefault(Action): 250 251 preconditions = [MaybeExistsAsNumber(), IsUnlockedCondition()] 252 effect = ArithmeticEffect(-1) 253 postconditions = [ExistsAsNumber()] 254 255###################################################################### 256# Driver 257###################################################################### 258 259class TestFile(object): 260 261 def __init__(self, path, n=10): 262 self.tmpfilenames = ["%s_%d.c.tmp" % (path, i) for i in range(n)] 263 self.files = [open(tfn, "w") for tfn in self.tmpfilenames] 264 self.seq = [list() for f in self.files] 265 self.index = 0 266 267 def finish(self): 268 for f in self.files: 269 f.close() 270 271 for tfn in self.tmpfilenames: 272 nfn = tfn[:-4] 273 assert (nfn + '.tmp') == tfn 274 os.rename(tfn, nfn) 275 276 def nextfile(self): 277 self.index = self.index + 1 278 if self.index >= len(self.files): 279 self.index = 0 280 281 def write(self, s): 282 self.files[self.index].write(s) 283 284 def addseq(self, seq): 285 self.seq[self.index].append(seq) 286 287class EngineTestAppDriver(Driver): 288 289 def __init__(self, writer=sys.stdout): 290 self.writer = writer 291 292 def output(self, s): 293 self.writer.write(s) 294 295 def preSuite(self, seq): 296 files = [self.writer] 297 if isinstance(self.writer, TestFile): 298 files = self.writer.files 299 for f in files: 300 f.write('#include "tests/suite_stubs.h"\n\n') 301 302 def testName(self, seq): 303 return 'test_' + '_'.join(a.name for a in seq) 304 305 def shouldRunSequence(self, seq): 306 # Skip any sequence that leads to known failures 307 ok = True 308 sclasses = [type(a) for a in seq] 309 # A list of lists of classes such that any test that includes 310 # the inner list in order should be skipped. 311 bads = [] 312 for b in bads: 313 try: 314 nextIdx = 0 315 for c in b: 316 nextIdx = sclasses.index(c, nextIdx) + 1 317 ok = False 318 except ValueError: 319 pass # Didn't find it, move in 320 321 return ok 322 323 def startSequence(self, seq): 324 if isinstance(self.writer, TestFile): 325 self.writer.nextfile() 326 self.writer.addseq(seq) 327 328 f = "static enum test_result %s" % self.testName(seq) 329 self.output(("%s(ENGINE_HANDLE *h,\n%sENGINE_HANDLE_V1 *h1) {\n" 330 % (f, " " * (len(f) + 1)))) 331 332 self.handled = self.shouldRunSequence(seq) 333 334 if not self.handled: 335 self.output(" (void)h;\n") 336 self.output(" (void)h1;\n") 337 self.output(" return PENDING;\n") 338 self.output("}\n\n") 339 340 def startAction(self, action): 341 if not self.handled: 342 return 343 344 if isinstance(action, Delay): 345 s = " delay(expiry+1);" 346 elif isinstance(action, WaitForLock): 347 s = " delay(locktime+1);" 348 elif isinstance(action, Flush): 349 s = " flush(h, h1);" 350 elif isinstance(action, Delete): 351 s = ' del(h, h1);' 352 else: 353 s = ' %s(h, h1);' % (action.name) 354 self.output(s + "\n") 355 356 def _writeList(self, writer, fname, seq): 357 writer.write("""MEMCACHED_PUBLIC_API 358engine_test_t* %s(void) { 359 360 static engine_test_t tests[] = { 361 362""" % fname) 363 for seq in sorted(seq): 364 writer.write(' {"%s",\n %s,\n NULL, teardown, NULL, NULL, NULL},\n' % ( 365 ', '.join(a.name for a in seq), 366 self.testName(seq))) 367 368 writer.write(""" {NULL, NULL, NULL, NULL, NULL, NULL, NULL} 369 }; 370 return tests; 371} 372""") 373 374 def postSuite(self, seq): 375 if isinstance(self.writer, TestFile): 376 for i in range(len(self.writer.files)): 377 self._writeList(self.writer.files[i], 378 'get_tests_%d' % i, 379 self.writer.seq[i]) 380 else: 381 self._writeList(self.writer, 'get_tests', seq) 382 383 def endSequence(self, seq, state): 384 if not self.handled: 385 return 386 387 val = state.get(TESTKEY) 388 if val: 389 self.output(' checkValue(h, h1, "%s");\n' % val) 390 else: 391 self.output(' assertNotExists(h, h1);\n') 392 self.output(" return SUCCESS;\n") 393 self.output("}\n\n") 394 395 def endAction(self, action, state, errored): 396 if not self.handled: 397 return 398 399 value = state.get(TESTKEY) 400 if value: 401 vs = ' /* value is "%s" */\n' % value 402 else: 403 vs = ' /* value is not defined */\n' 404 405 if errored: 406 self.output(" assertHasError();" + vs) 407 else: 408 self.output(" assertHasNoError();" + vs) 409 410if __name__ == '__main__': 411 w = TestFile('generated_suite') 412 breakdancer.runTest(breakdancer.findActions(globals().values()), 413 EngineTestAppDriver(w)) 414 w.finish() 415