42 import silenttestrunner |
42 import silenttestrunner |
43 import subprocess |
43 import subprocess |
44 |
44 |
45 from hypothesis.errors import HypothesisException |
45 from hypothesis.errors import HypothesisException |
46 from hypothesis.stateful import ( |
46 from hypothesis.stateful import ( |
47 rule, RuleBasedStateMachine, Bundle, precondition) |
47 rule, |
|
48 RuleBasedStateMachine, |
|
49 Bundle, |
|
50 precondition, |
|
51 ) |
48 from hypothesis import settings, note, strategies as st |
52 from hypothesis import settings, note, strategies as st |
49 from hypothesis.configuration import set_hypothesis_home_dir |
53 from hypothesis.configuration import set_hypothesis_home_dir |
50 from hypothesis.database import ExampleDatabase |
54 from hypothesis.database import ExampleDatabase |
51 |
55 |
52 testdir = os.path.abspath(os.environ["TESTDIR"]) |
56 testdir = os.path.abspath(os.environ["TESTDIR"]) |
92 filecharacters = ( |
96 filecharacters = ( |
93 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" |
97 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" |
94 "[]^_`;=@{}~ !#$%&'()+,-" |
98 "[]^_`;=@{}~ !#$%&'()+,-" |
95 ) |
99 ) |
96 |
100 |
97 files = st.text(filecharacters, min_size=1).map(lambda x: x.strip()).filter( |
101 files = ( |
98 bool).map(lambda s: s.encode('ascii')) |
102 st.text(filecharacters, min_size=1) |
99 |
103 .map(lambda x: x.strip()) |
100 safetext = st.text(st.characters( |
104 .filter(bool) |
101 min_codepoint=1, max_codepoint=127, |
105 .map(lambda s: s.encode('ascii')) |
102 blacklist_categories=('Cc', 'Cs')), min_size=1).map( |
|
103 lambda s: s.encode('utf-8') |
|
104 ) |
106 ) |
105 |
107 |
|
108 safetext = st.text( |
|
109 st.characters( |
|
110 min_codepoint=1, max_codepoint=127, blacklist_categories=('Cc', 'Cs') |
|
111 ), |
|
112 min_size=1, |
|
113 ).map(lambda s: s.encode('utf-8')) |
|
114 |
106 extensions = st.sampled_from(('shelve', 'mq', 'blackbox',)) |
115 extensions = st.sampled_from(('shelve', 'mq', 'blackbox',)) |
|
116 |
107 |
117 |
108 @contextmanager |
118 @contextmanager |
109 def acceptableerrors(*args): |
119 def acceptableerrors(*args): |
110 """Sometimes we know an operation we're about to perform might fail, and |
120 """Sometimes we know an operation we're about to perform might fail, and |
111 we're OK with some of the failures. In those cases this may be used as a |
121 we're OK with some of the failures. In those cases this may be used as a |
186 path = os.path.join(testtmp, "test-generated.t") |
198 path = os.path.join(testtmp, "test-generated.t") |
187 with open(path, 'w') as o: |
199 with open(path, 'w') as o: |
188 o.write(ttest + os.linesep) |
200 o.write(ttest + os.linesep) |
189 with open(os.devnull, "w") as devnull: |
201 with open(os.devnull, "w") as devnull: |
190 rewriter = subprocess.Popen( |
202 rewriter = subprocess.Popen( |
191 [runtests, "--local", "-i", path], stdin=subprocess.PIPE, |
203 [runtests, "--local", "-i", path], |
192 stdout=devnull, stderr=devnull, |
204 stdin=subprocess.PIPE, |
|
205 stdout=devnull, |
|
206 stderr=devnull, |
193 ) |
207 ) |
194 rewriter.communicate("yes") |
208 rewriter.communicate("yes") |
195 with open(path, 'r') as i: |
209 with open(path, 'r') as i: |
196 ttest = i.read() |
210 ttest = i.read() |
197 |
211 |
198 e = None |
212 e = None |
199 if not self.failed: |
213 if not self.failed: |
200 try: |
214 try: |
201 output = subprocess.check_output([ |
215 output = subprocess.check_output( |
202 runtests, path, "--local", "--pure" |
216 [runtests, path, "--local", "--pure"], |
203 ], stderr=subprocess.STDOUT) |
217 stderr=subprocess.STDOUT, |
|
218 ) |
204 assert "Ran 1 test" in output, output |
219 assert "Ran 1 test" in output, output |
205 for ext in ( |
220 for ext in self.all_extensions - self.non_skippable_extensions: |
206 self.all_extensions - self.non_skippable_extensions |
221 tf = os.path.join( |
207 ): |
222 testtmp, "test-generated-no-%s.t" % (ext,) |
208 tf = os.path.join(testtmp, "test-generated-no-%s.t" % ( |
223 ) |
209 ext, |
|
210 )) |
|
211 with open(tf, 'w') as o: |
224 with open(tf, 'w') as o: |
212 for l in ttest.splitlines(): |
225 for l in ttest.splitlines(): |
213 if l.startswith(" $ hg"): |
226 if l.startswith(" $ hg"): |
214 l = l.replace( |
227 l = l.replace( |
215 "--config %s=" % ( |
228 "--config %s=" % (extensionconfigkey(ext),), |
216 extensionconfigkey(ext),), "") |
229 "", |
|
230 ) |
217 o.write(l + os.linesep) |
231 o.write(l + os.linesep) |
218 with open(tf, 'r') as r: |
232 with open(tf, 'r') as r: |
219 t = r.read() |
233 t = r.read() |
220 assert ext not in t, t |
234 assert ext not in t, t |
221 output = subprocess.check_output([ |
235 output = subprocess.check_output( |
222 runtests, tf, "--local", |
236 [runtests, tf, "--local",], stderr=subprocess.STDOUT |
223 ], stderr=subprocess.STDOUT) |
237 ) |
224 assert "Ran 1 test" in output, output |
238 assert "Ran 1 test" in output, output |
225 except subprocess.CalledProcessError as e: |
239 except subprocess.CalledProcessError as e: |
226 note(e.output) |
240 note(e.output) |
227 if self.failed or e is not None: |
241 if self.failed or e is not None: |
228 with open(savefile, "wb") as o: |
242 with open(savefile, "wb") as o: |
268 # Section: Set up basic data |
283 # Section: Set up basic data |
269 # This section has no side effects but generates data that we will want |
284 # This section has no side effects but generates data that we will want |
270 # to use later. |
285 # to use later. |
271 @rule( |
286 @rule( |
272 target=paths, |
287 target=paths, |
273 source=st.lists(files, min_size=1).map(lambda l: os.path.join(*l))) |
288 source=st.lists(files, min_size=1).map(lambda l: os.path.join(*l)), |
|
289 ) |
274 def genpath(self, source): |
290 def genpath(self, source): |
275 return source |
291 return source |
276 |
292 |
277 @rule( |
293 @rule( |
278 target=committimes, |
294 target=committimes, |
279 when=datetimes(min_year=1970, max_year=2038) | st.none()) |
295 when=datetimes(min_year=1970, max_year=2038) | st.none(), |
|
296 ) |
280 def gentime(self, when): |
297 def gentime(self, when): |
281 return when |
298 return when |
282 |
299 |
283 @rule( |
300 @rule( |
284 target=contents, |
301 target=contents, |
285 content=st.one_of( |
302 content=st.one_of( |
286 st.binary(), |
303 st.binary(), st.text().map(lambda x: x.encode('utf-8')) |
287 st.text().map(lambda x: x.encode('utf-8')) |
304 ), |
288 )) |
305 ) |
289 def gencontent(self, content): |
306 def gencontent(self, content): |
290 return content |
307 return content |
291 |
308 |
292 @rule( |
309 @rule( |
293 target=branches, |
310 target=branches, name=safetext, |
294 name=safetext, |
|
295 ) |
311 ) |
296 def genbranch(self, name): |
312 def genbranch(self, name): |
297 return name |
313 return name |
298 |
314 |
299 @rule(target=paths, source=paths) |
315 @rule(target=paths, source=paths) |
320 # of the current path. This will cause mkdirp to fail with this |
336 # of the current path. This will cause mkdirp to fail with this |
321 # error. We just turn this into a no-op in that case. |
337 # error. We just turn this into a no-op in that case. |
322 return |
338 return |
323 with open(path, 'wb') as o: |
339 with open(path, 'wb') as o: |
324 o.write(content) |
340 o.write(content) |
325 self.log.append(( |
341 self.log.append( |
326 "$ python -c 'import binascii; " |
342 ( |
327 "print(binascii.unhexlify(\"%s\"))' > %s") % ( |
343 "$ python -c 'import binascii; " |
328 binascii.hexlify(content), |
344 "print(binascii.unhexlify(\"%s\"))' > %s" |
329 pipes.quote(path), |
345 ) |
330 )) |
346 % (binascii.hexlify(content), pipes.quote(path),) |
|
347 ) |
331 |
348 |
332 @rule(path=paths) |
349 @rule(path=paths) |
333 def addpath(self, path): |
350 def addpath(self, path): |
334 if os.path.exists(path): |
351 if os.path.exists(path): |
335 self.hg("add", "--", path) |
352 self.hg("add", "--", path) |
336 |
353 |
337 @rule(path=paths) |
354 @rule(path=paths) |
338 def forgetpath(self, path): |
355 def forgetpath(self, path): |
339 if os.path.exists(path): |
356 if os.path.exists(path): |
340 with acceptableerrors( |
357 with acceptableerrors("file is already untracked",): |
341 "file is already untracked", |
|
342 ): |
|
343 self.hg("forget", "--", path) |
358 self.hg("forget", "--", path) |
344 |
359 |
345 @rule(s=st.none() | st.integers(0, 100)) |
360 @rule(s=st.none() | st.integers(0, 100)) |
346 def addremove(self, s): |
361 def addremove(self, s): |
347 args = ["addremove"] |
362 args = ["addremove"] |
402 @property |
418 @property |
403 def config(self): |
419 def config(self): |
404 return self.configperrepo.setdefault(self.currentrepo, {}) |
420 return self.configperrepo.setdefault(self.currentrepo, {}) |
405 |
421 |
406 @rule( |
422 @rule( |
407 target=repos, |
423 target=repos, source=repos, name=reponames, |
408 source=repos, |
|
409 name=reponames, |
|
410 ) |
424 ) |
411 def clone(self, source, name): |
425 def clone(self, source, name): |
412 if not os.path.exists(os.path.join("..", name)): |
426 if not os.path.exists(os.path.join("..", name)): |
413 self.cd("..") |
427 self.cd("..") |
414 self.hg("clone", source, name) |
428 self.hg("clone", source, name) |
415 self.cd(name) |
429 self.cd(name) |
416 return name |
430 return name |
417 |
431 |
418 @rule( |
432 @rule( |
419 target=repos, |
433 target=repos, name=reponames, |
420 name=reponames, |
|
421 ) |
434 ) |
422 def fresh(self, name): |
435 def fresh(self, name): |
423 if not os.path.exists(os.path.join("..", name)): |
436 if not os.path.exists(os.path.join("..", name)): |
424 self.cd("..") |
437 self.cd("..") |
425 self.mkdirp(name) |
438 self.mkdirp(name) |
438 return "repo1" |
451 return "repo1" |
439 |
452 |
440 @rule() |
453 @rule() |
441 def pull(self, repo=repos): |
454 def pull(self, repo=repos): |
442 with acceptableerrors( |
455 with acceptableerrors( |
443 "repository default not found", |
456 "repository default not found", "repository is unrelated", |
444 "repository is unrelated", |
|
445 ): |
457 ): |
446 self.hg("pull") |
458 self.hg("pull") |
447 |
459 |
448 @rule(newbranch=st.booleans()) |
460 @rule(newbranch=st.booleans()) |
449 def push(self, newbranch): |
461 def push(self, newbranch): |
450 with acceptableerrors( |
462 with acceptableerrors( |
451 "default repository not configured", |
463 "default repository not configured", "no changes found", |
452 "no changes found", |
|
453 ): |
464 ): |
454 if newbranch: |
465 if newbranch: |
455 self.hg("push", "--new-branch") |
466 self.hg("push", "--new-branch") |
456 else: |
467 else: |
457 with acceptableerrors( |
468 with acceptableerrors("creates new branches"): |
458 "creates new branches" |
|
459 ): |
|
460 self.hg("push") |
469 self.hg("push") |
461 |
470 |
462 # Section: Simple side effect free "check" operations |
471 # Section: Simple side effect free "check" operations |
463 @rule() |
472 @rule() |
464 def log(self): |
473 def log(self): |
496 self.hg("branch", "--", branch) |
505 self.hg("branch", "--", branch) |
497 |
506 |
498 @rule(branch=branches, clean=st.booleans()) |
507 @rule(branch=branches, clean=st.booleans()) |
499 def update(self, branch, clean): |
508 def update(self, branch, clean): |
500 with acceptableerrors( |
509 with acceptableerrors( |
501 'unknown revision', |
510 'unknown revision', 'parse error', |
502 'parse error', |
|
503 ): |
511 ): |
504 if clean: |
512 if clean: |
505 self.hg("update", "-C", "--", branch) |
513 self.hg("update", "-C", "--", branch) |
506 else: |
514 else: |
507 self.hg("update", "--", branch) |
515 self.hg("update", "--", branch) |
553 self.underlying.delete(key, value) |
562 self.underlying.delete(key, value) |
554 |
563 |
555 def close(self): |
564 def close(self): |
556 self.underlying.close() |
565 self.underlying.close() |
557 |
566 |
|
567 |
558 def extensionconfigkey(extension): |
568 def extensionconfigkey(extension): |
559 return "extensions." + extension |
569 return "extensions." + extension |
560 |
570 |
|
571 |
561 settings.register_profile( |
572 settings.register_profile( |
562 'default', settings( |
573 'default', settings(timeout=300, stateful_step_count=50, max_examples=10,) |
563 timeout=300, |
|
564 stateful_step_count=50, |
|
565 max_examples=10, |
|
566 ) |
|
567 ) |
574 ) |
568 |
575 |
569 settings.register_profile( |
576 settings.register_profile( |
570 'fast', settings( |
577 'fast', |
|
578 settings( |
571 timeout=10, |
579 timeout=10, |
572 stateful_step_count=20, |
580 stateful_step_count=20, |
573 max_examples=5, |
581 max_examples=5, |
574 min_satisfying_examples=1, |
582 min_satisfying_examples=1, |
575 max_shrinks=0, |
583 max_shrinks=0, |
576 ) |
584 ), |
577 ) |
585 ) |
578 |
586 |
579 settings.register_profile( |
587 settings.register_profile( |
580 'continuous', settings( |
588 'continuous', |
|
589 settings( |
581 timeout=-1, |
590 timeout=-1, |
582 stateful_step_count=1000, |
591 stateful_step_count=1000, |
583 max_examples=10 ** 8, |
592 max_examples=10 ** 8, |
584 max_iterations=10 ** 8, |
593 max_iterations=10 ** 8, |
585 database=writeonlydatabase(settings.default.database) |
594 database=writeonlydatabase(settings.default.database), |
586 ) |
595 ), |
587 ) |
596 ) |
588 |
597 |
589 settings.load_profile(os.getenv('HYPOTHESIS_PROFILE', 'default')) |
598 settings.load_profile(os.getenv('HYPOTHESIS_PROFILE', 'default')) |
590 |
599 |
591 verifyingtest = verifyingstatemachine.TestCase |
600 verifyingtest = verifyingstatemachine.TestCase |