##// END OF EJS Templates
testing: test multiple repositories with Hypothesis...
David R. MacIver -
r28257:7ff725db default
parent child Browse files
Show More
@@ -1,438 +1,506 b''
1 from __future__ import print_function, absolute_import
1 from __future__ import print_function, absolute_import
2
2
3 """Fuzz testing for operations against a Mercurial repository
3 """Fuzz testing for operations against a Mercurial repository
4
4
5 This uses Hypothesis's stateful testing to generate random repository
5 This uses Hypothesis's stateful testing to generate random repository
6 operations and test Mercurial using them, both to see if there are any
6 operations and test Mercurial using them, both to see if there are any
7 unexpected errors and to compare different versions of it."""
7 unexpected errors and to compare different versions of it."""
8
8
9 import os
9 import os
10 import sys
10 import sys
11
11
12 # These tests require Hypothesis and pytz to be installed.
12 # These tests require Hypothesis and pytz to be installed.
13 # Running 'pip install hypothesis pytz' will achieve that.
13 # Running 'pip install hypothesis pytz' will achieve that.
14 # Note: This won't work if you're running Python < 2.7.
14 # Note: This won't work if you're running Python < 2.7.
15 try:
15 try:
16 from hypothesis.extra.datetime import datetimes
16 from hypothesis.extra.datetime import datetimes
17 except ImportError:
17 except ImportError:
18 sys.stderr.write("skipped: hypothesis or pytz not installed" + os.linesep)
18 sys.stderr.write("skipped: hypothesis or pytz not installed" + os.linesep)
19 sys.exit(80)
19 sys.exit(80)
20
20
21 # If you are running an old version of pip you may find that the enum34
21 # If you are running an old version of pip you may find that the enum34
22 # backport is not installed automatically. If so 'pip install enum34' will
22 # backport is not installed automatically. If so 'pip install enum34' will
23 # fix this problem.
23 # fix this problem.
24 try:
24 try:
25 import enum
25 import enum
26 assert enum # Silence pyflakes
26 assert enum # Silence pyflakes
27 except ImportError:
27 except ImportError:
28 sys.stderr.write("skipped: enum34 not installed" + os.linesep)
28 sys.stderr.write("skipped: enum34 not installed" + os.linesep)
29 sys.exit(80)
29 sys.exit(80)
30
30
31 import binascii
31 import binascii
32 from contextlib import contextmanager
32 from contextlib import contextmanager
33 import errno
33 import errno
34 import pipes
34 import pipes
35 import shutil
35 import shutil
36 import silenttestrunner
36 import silenttestrunner
37 import subprocess
37 import subprocess
38
38
39 from hypothesis.errors import HypothesisException
39 from hypothesis.errors import HypothesisException
40 from hypothesis.stateful import rule, RuleBasedStateMachine, Bundle
40 from hypothesis.stateful import rule, RuleBasedStateMachine, Bundle
41 from hypothesis import settings, note, strategies as st
41 from hypothesis import settings, note, strategies as st
42 from hypothesis.configuration import set_hypothesis_home_dir
42 from hypothesis.configuration import set_hypothesis_home_dir
43
43
44 testdir = os.path.abspath(os.environ["TESTDIR"])
44 testdir = os.path.abspath(os.environ["TESTDIR"])
45
45
46 # We store Hypothesis examples here rather in the temporary test directory
46 # We store Hypothesis examples here rather in the temporary test directory
47 # so that when rerunning a failing test this always results in refinding the
47 # so that when rerunning a failing test this always results in refinding the
48 # previous failure. This directory is in .hgignore and should not be checked in
48 # previous failure. This directory is in .hgignore and should not be checked in
49 # but is useful to have for development.
49 # but is useful to have for development.
50 set_hypothesis_home_dir(os.path.join(testdir, ".hypothesis"))
50 set_hypothesis_home_dir(os.path.join(testdir, ".hypothesis"))
51
51
52 runtests = os.path.join(os.environ["RUNTESTDIR"], "run-tests.py")
52 runtests = os.path.join(os.environ["RUNTESTDIR"], "run-tests.py")
53 testtmp = os.environ["TESTTMP"]
53 testtmp = os.environ["TESTTMP"]
54 assert os.path.isdir(testtmp)
54 assert os.path.isdir(testtmp)
55
55
56 generatedtests = os.path.join(testdir, "hypothesis-generated")
56 generatedtests = os.path.join(testdir, "hypothesis-generated")
57
57
58 try:
58 try:
59 os.makedirs(generatedtests)
59 os.makedirs(generatedtests)
60 except OSError:
60 except OSError:
61 pass
61 pass
62
62
63 # We write out generated .t files to a file in order to ease debugging and to
63 # We write out generated .t files to a file in order to ease debugging and to
64 # give a starting point for turning failures Hypothesis finds into normal
64 # give a starting point for turning failures Hypothesis finds into normal
65 # tests. In order to ensure that multiple copies of this test can be run in
65 # tests. In order to ensure that multiple copies of this test can be run in
66 # parallel we use atomic file create to ensure that we always get a unique
66 # parallel we use atomic file create to ensure that we always get a unique
67 # name.
67 # name.
68 file_index = 0
68 file_index = 0
69 while True:
69 while True:
70 file_index += 1
70 file_index += 1
71 savefile = os.path.join(generatedtests, "test-generated-%d.t" % (
71 savefile = os.path.join(generatedtests, "test-generated-%d.t" % (
72 file_index,
72 file_index,
73 ))
73 ))
74 try:
74 try:
75 os.close(os.open(savefile, os.O_CREAT | os.O_EXCL | os.O_WRONLY))
75 os.close(os.open(savefile, os.O_CREAT | os.O_EXCL | os.O_WRONLY))
76 break
76 break
77 except OSError as e:
77 except OSError as e:
78 if e.errno != errno.EEXIST:
78 if e.errno != errno.EEXIST:
79 raise
79 raise
80 assert os.path.exists(savefile)
80 assert os.path.exists(savefile)
81
81
82 hgrc = os.path.join(".hg", "hgrc")
82 hgrc = os.path.join(".hg", "hgrc")
83
83
84 filecharacters = (
84 filecharacters = (
85 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
85 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
86 "[]^_`;=@{}~ !#$%&'()+,-"
86 "[]^_`;=@{}~ !#$%&'()+,-"
87 )
87 )
88
88
89 files = st.text(filecharacters, min_size=1).map(lambda x: x.strip()).filter(
89 files = st.text(filecharacters, min_size=1).map(lambda x: x.strip()).filter(
90 bool).map(lambda s: s.encode('ascii'))
90 bool).map(lambda s: s.encode('ascii'))
91
91
92 safetext = st.text(st.characters(
92 safetext = st.text(st.characters(
93 min_codepoint=1, max_codepoint=127,
93 min_codepoint=1, max_codepoint=127,
94 blacklist_categories=('Cc', 'Cs')), min_size=1).map(
94 blacklist_categories=('Cc', 'Cs')), min_size=1).map(
95 lambda s: s.encode('utf-8')
95 lambda s: s.encode('utf-8')
96 )
96 )
97
97
98 @contextmanager
98 @contextmanager
99 def acceptableerrors(*args):
99 def acceptableerrors(*args):
100 """Sometimes we know an operation we're about to perform might fail, and
100 """Sometimes we know an operation we're about to perform might fail, and
101 we're OK with some of the failures. In those cases this may be used as a
101 we're OK with some of the failures. In those cases this may be used as a
102 context manager and will swallow expected failures, as identified by
102 context manager and will swallow expected failures, as identified by
103 substrings of the error message Mercurial emits."""
103 substrings of the error message Mercurial emits."""
104 try:
104 try:
105 yield
105 yield
106 except subprocess.CalledProcessError as e:
106 except subprocess.CalledProcessError as e:
107 if not any(a in e.output for a in args):
107 if not any(a in e.output for a in args):
108 note(e.output)
108 note(e.output)
109 raise
109 raise
110
110
111 reponames = st.text("abcdefghijklmnopqrstuvwxyz01234556789", min_size=1).map(
112 lambda s: s.encode('ascii')
113 )
114
111 class verifyingstatemachine(RuleBasedStateMachine):
115 class verifyingstatemachine(RuleBasedStateMachine):
112 """This defines the set of acceptable operations on a Mercurial repository
116 """This defines the set of acceptable operations on a Mercurial repository
113 using Hypothesis's RuleBasedStateMachine.
117 using Hypothesis's RuleBasedStateMachine.
114
118
115 The general concept is that we manage multiple repositories inside a
119 The general concept is that we manage multiple repositories inside a
116 repos/ directory in our temporary test location. Some of these are freshly
120 repos/ directory in our temporary test location. Some of these are freshly
117 inited, some are clones of the others. Our current working directory is
121 inited, some are clones of the others. Our current working directory is
118 always inside one of these repositories while the tests are running.
122 always inside one of these repositories while the tests are running.
119
123
120 Hypothesis then performs a series of operations against these repositories,
124 Hypothesis then performs a series of operations against these repositories,
121 including hg commands, generating contents and editing the .hgrc file.
125 including hg commands, generating contents and editing the .hgrc file.
122 If these operations fail in unexpected ways or behave differently in
126 If these operations fail in unexpected ways or behave differently in
123 different configurations of Mercurial, the test will fail and a minimized
127 different configurations of Mercurial, the test will fail and a minimized
124 .t test file will be written to the hypothesis-generated directory to
128 .t test file will be written to the hypothesis-generated directory to
125 exhibit that failure.
129 exhibit that failure.
126
130
127 Operations are defined as methods with @rule() decorators. See the
131 Operations are defined as methods with @rule() decorators. See the
128 Hypothesis documentation at
132 Hypothesis documentation at
129 http://hypothesis.readthedocs.org/en/release/stateful.html for more
133 http://hypothesis.readthedocs.org/en/release/stateful.html for more
130 details."""
134 details."""
131
135
132 # A bundle is a reusable collection of previously generated data which may
136 # A bundle is a reusable collection of previously generated data which may
133 # be provided as arguments to future operations.
137 # be provided as arguments to future operations.
138 repos = Bundle('repos')
134 paths = Bundle('paths')
139 paths = Bundle('paths')
135 contents = Bundle('contents')
140 contents = Bundle('contents')
136 branches = Bundle('branches')
141 branches = Bundle('branches')
137 committimes = Bundle('committimes')
142 committimes = Bundle('committimes')
138
143
139 def __init__(self):
144 def __init__(self):
140 super(verifyingstatemachine, self).__init__()
145 super(verifyingstatemachine, self).__init__()
141 self.repodir = os.path.join(testtmp, "repo")
146 self.repodir = os.path.join(testtmp, "repos")
142 if os.path.exists(self.repodir):
147 if os.path.exists(self.repodir):
143 shutil.rmtree(self.repodir)
148 shutil.rmtree(self.repodir)
144 os.chdir(testtmp)
149 os.chdir(testtmp)
145 self.log = []
150 self.log = []
146 self.failed = False
151 self.failed = False
147
152
148 self.mkdirp("repo")
153 self.mkdirp("repos")
149 self.cd("repo")
154 self.cd("repos")
155 self.mkdirp("repo1")
156 self.cd("repo1")
150 self.hg("init")
157 self.hg("init")
151
158
152 def teardown(self):
159 def teardown(self):
153 """On teardown we clean up after ourselves as usual, but we also
160 """On teardown we clean up after ourselves as usual, but we also
154 do some additional testing: We generate a .t file based on our test
161 do some additional testing: We generate a .t file based on our test
155 run using run-test.py -i to get the correct output.
162 run using run-test.py -i to get the correct output.
156
163
157 We then test it in a number of other configurations, verifying that
164 We then test it in a number of other configurations, verifying that
158 each passes the same test."""
165 each passes the same test."""
159 super(verifyingstatemachine, self).teardown()
166 super(verifyingstatemachine, self).teardown()
160 try:
167 try:
161 shutil.rmtree(self.repodir)
168 shutil.rmtree(self.repodir)
162 except OSError:
169 except OSError:
163 pass
170 pass
164 ttest = os.linesep.join(" " + l for l in self.log)
171 ttest = os.linesep.join(" " + l for l in self.log)
165 os.chdir(testtmp)
172 os.chdir(testtmp)
166 path = os.path.join(testtmp, "test-generated.t")
173 path = os.path.join(testtmp, "test-generated.t")
167 with open(path, 'w') as o:
174 with open(path, 'w') as o:
168 o.write(ttest + os.linesep)
175 o.write(ttest + os.linesep)
169 with open(os.devnull, "w") as devnull:
176 with open(os.devnull, "w") as devnull:
170 rewriter = subprocess.Popen(
177 rewriter = subprocess.Popen(
171 [runtests, "--local", "-i", path], stdin=subprocess.PIPE,
178 [runtests, "--local", "-i", path], stdin=subprocess.PIPE,
172 stdout=devnull, stderr=devnull,
179 stdout=devnull, stderr=devnull,
173 )
180 )
174 rewriter.communicate("yes")
181 rewriter.communicate("yes")
175 with open(path, 'r') as i:
182 with open(path, 'r') as i:
176 ttest = i.read()
183 ttest = i.read()
177
184
178 e = None
185 e = None
179 if not self.failed:
186 if not self.failed:
180 try:
187 try:
181 output = subprocess.check_output([
188 output = subprocess.check_output([
182 runtests, path, "--local", "--pure"
189 runtests, path, "--local", "--pure"
183 ], stderr=subprocess.STDOUT)
190 ], stderr=subprocess.STDOUT)
184 assert "Ran 1 test" in output, output
191 assert "Ran 1 test" in output, output
185 except subprocess.CalledProcessError as e:
192 except subprocess.CalledProcessError as e:
186 note(e.output)
193 note(e.output)
187 finally:
194 finally:
188 os.unlink(path)
195 os.unlink(path)
189 try:
196 try:
190 os.unlink(path + ".err")
197 os.unlink(path + ".err")
191 except OSError:
198 except OSError:
192 pass
199 pass
193 if self.failed or e is not None:
200 if self.failed or e is not None:
194 with open(savefile, "wb") as o:
201 with open(savefile, "wb") as o:
195 o.write(ttest)
202 o.write(ttest)
196 if e is not None:
203 if e is not None:
197 raise e
204 raise e
198
205
199 def execute_step(self, step):
206 def execute_step(self, step):
200 try:
207 try:
201 return super(verifyingstatemachine, self).execute_step(step)
208 return super(verifyingstatemachine, self).execute_step(step)
202 except (HypothesisException, KeyboardInterrupt):
209 except (HypothesisException, KeyboardInterrupt):
203 raise
210 raise
204 except Exception:
211 except Exception:
205 self.failed = True
212 self.failed = True
206 raise
213 raise
207
214
208 # Section: Basic commands.
215 # Section: Basic commands.
209 def mkdirp(self, path):
216 def mkdirp(self, path):
210 if os.path.exists(path):
217 if os.path.exists(path):
211 return
218 return
212 self.log.append(
219 self.log.append(
213 "$ mkdir -p -- %s" % (pipes.quote(os.path.relpath(path)),))
220 "$ mkdir -p -- %s" % (pipes.quote(os.path.relpath(path)),))
214 os.makedirs(path)
221 os.makedirs(path)
215
222
216 def cd(self, path):
223 def cd(self, path):
217 path = os.path.relpath(path)
224 path = os.path.relpath(path)
218 if path == ".":
225 if path == ".":
219 return
226 return
220 os.chdir(path)
227 os.chdir(path)
221 self.log.append("$ cd -- %s" % (pipes.quote(path),))
228 self.log.append("$ cd -- %s" % (pipes.quote(path),))
222
229
223 def hg(self, *args):
230 def hg(self, *args):
224 self.command("hg", *args)
231 self.command("hg", *args)
225
232
226 def command(self, *args):
233 def command(self, *args):
227 self.log.append("$ " + ' '.join(map(pipes.quote, args)))
234 self.log.append("$ " + ' '.join(map(pipes.quote, args)))
228 subprocess.check_output(args, stderr=subprocess.STDOUT)
235 subprocess.check_output(args, stderr=subprocess.STDOUT)
229
236
230 # Section: Set up basic data
237 # Section: Set up basic data
231 # This section has no side effects but generates data that we will want
238 # This section has no side effects but generates data that we will want
232 # to use later.
239 # to use later.
233 @rule(
240 @rule(
234 target=paths,
241 target=paths,
235 source=st.lists(files, min_size=1).map(lambda l: os.path.join(*l)))
242 source=st.lists(files, min_size=1).map(lambda l: os.path.join(*l)))
236 def genpath(self, source):
243 def genpath(self, source):
237 return source
244 return source
238
245
239 @rule(
246 @rule(
240 target=committimes,
247 target=committimes,
241 when=datetimes(min_year=1970, max_year=2038) | st.none())
248 when=datetimes(min_year=1970, max_year=2038) | st.none())
242 def gentime(self, when):
249 def gentime(self, when):
243 return when
250 return when
244
251
245 @rule(
252 @rule(
246 target=contents,
253 target=contents,
247 content=st.one_of(
254 content=st.one_of(
248 st.binary(),
255 st.binary(),
249 st.text().map(lambda x: x.encode('utf-8'))
256 st.text().map(lambda x: x.encode('utf-8'))
250 ))
257 ))
251 def gencontent(self, content):
258 def gencontent(self, content):
252 return content
259 return content
253
260
254 @rule(
261 @rule(
255 target=branches,
262 target=branches,
256 name=safetext,
263 name=safetext,
257 )
264 )
258 def genbranch(self, name):
265 def genbranch(self, name):
259 return name
266 return name
260
267
261 @rule(target=paths, source=paths)
268 @rule(target=paths, source=paths)
262 def lowerpath(self, source):
269 def lowerpath(self, source):
263 return source.lower()
270 return source.lower()
264
271
265 @rule(target=paths, source=paths)
272 @rule(target=paths, source=paths)
266 def upperpath(self, source):
273 def upperpath(self, source):
267 return source.upper()
274 return source.upper()
268
275
269 # Section: Basic path operations
276 # Section: Basic path operations
270 @rule(path=paths, content=contents)
277 @rule(path=paths, content=contents)
271 def writecontent(self, path, content):
278 def writecontent(self, path, content):
272 self.unadded_changes = True
279 self.unadded_changes = True
273 if os.path.isdir(path):
280 if os.path.isdir(path):
274 return
281 return
275 parent = os.path.dirname(path)
282 parent = os.path.dirname(path)
276 if parent:
283 if parent:
277 try:
284 try:
278 self.mkdirp(parent)
285 self.mkdirp(parent)
279 except OSError:
286 except OSError:
280 # It may be the case that there is a regular file that has
287 # It may be the case that there is a regular file that has
281 # previously been created that has the same name as an ancestor
288 # previously been created that has the same name as an ancestor
282 # of the current path. This will cause mkdirp to fail with this
289 # of the current path. This will cause mkdirp to fail with this
283 # error. We just turn this into a no-op in that case.
290 # error. We just turn this into a no-op in that case.
284 return
291 return
285 with open(path, 'wb') as o:
292 with open(path, 'wb') as o:
286 o.write(content)
293 o.write(content)
287 self.log.append((
294 self.log.append((
288 "$ python -c 'import binascii; "
295 "$ python -c 'import binascii; "
289 "print(binascii.unhexlify(\"%s\"))' > %s") % (
296 "print(binascii.unhexlify(\"%s\"))' > %s") % (
290 binascii.hexlify(content),
297 binascii.hexlify(content),
291 pipes.quote(path),
298 pipes.quote(path),
292 ))
299 ))
293
300
294 @rule(path=paths)
301 @rule(path=paths)
295 def addpath(self, path):
302 def addpath(self, path):
296 if os.path.exists(path):
303 if os.path.exists(path):
297 self.hg("add", "--", path)
304 self.hg("add", "--", path)
298
305
299 @rule(path=paths)
306 @rule(path=paths)
300 def forgetpath(self, path):
307 def forgetpath(self, path):
301 if os.path.exists(path):
308 if os.path.exists(path):
302 with acceptableerrors(
309 with acceptableerrors(
303 "file is already untracked",
310 "file is already untracked",
304 ):
311 ):
305 self.hg("forget", "--", path)
312 self.hg("forget", "--", path)
306
313
307 @rule(s=st.none() | st.integers(0, 100))
314 @rule(s=st.none() | st.integers(0, 100))
308 def addremove(self, s):
315 def addremove(self, s):
309 args = ["addremove"]
316 args = ["addremove"]
310 if s is not None:
317 if s is not None:
311 args.extend(["-s", str(s)])
318 args.extend(["-s", str(s)])
312 self.hg(*args)
319 self.hg(*args)
313
320
314 @rule(path=paths)
321 @rule(path=paths)
315 def removepath(self, path):
322 def removepath(self, path):
316 if os.path.exists(path):
323 if os.path.exists(path):
317 with acceptableerrors(
324 with acceptableerrors(
318 'file is untracked',
325 'file is untracked',
319 'file has been marked for add',
326 'file has been marked for add',
320 'file is modified',
327 'file is modified',
321 ):
328 ):
322 self.hg("remove", "--", path)
329 self.hg("remove", "--", path)
323
330
324 @rule(
331 @rule(
325 message=safetext,
332 message=safetext,
326 amend=st.booleans(),
333 amend=st.booleans(),
327 when=committimes,
334 when=committimes,
328 addremove=st.booleans(),
335 addremove=st.booleans(),
329 secret=st.booleans(),
336 secret=st.booleans(),
330 close_branch=st.booleans(),
337 close_branch=st.booleans(),
331 )
338 )
332 def maybecommit(
339 def maybecommit(
333 self, message, amend, when, addremove, secret, close_branch
340 self, message, amend, when, addremove, secret, close_branch
334 ):
341 ):
335 command = ["commit"]
342 command = ["commit"]
336 errors = ["nothing changed"]
343 errors = ["nothing changed"]
337 if amend:
344 if amend:
338 errors.append("cannot amend public changesets")
345 errors.append("cannot amend public changesets")
339 command.append("--amend")
346 command.append("--amend")
340 command.append("-m" + pipes.quote(message))
347 command.append("-m" + pipes.quote(message))
341 if secret:
348 if secret:
342 command.append("--secret")
349 command.append("--secret")
343 if close_branch:
350 if close_branch:
344 command.append("--close-branch")
351 command.append("--close-branch")
345 errors.append("can only close branch heads")
352 errors.append("can only close branch heads")
346 if addremove:
353 if addremove:
347 command.append("--addremove")
354 command.append("--addremove")
348 if when is not None:
355 if when is not None:
349 if when.year == 1970:
356 if when.year == 1970:
350 errors.append('negative date value')
357 errors.append('negative date value')
351 if when.year == 2038:
358 if when.year == 2038:
352 errors.append('exceeds 32 bits')
359 errors.append('exceeds 32 bits')
353 command.append("--date=%s" % (
360 command.append("--date=%s" % (
354 when.strftime('%Y-%m-%d %H:%M:%S %z'),))
361 when.strftime('%Y-%m-%d %H:%M:%S %z'),))
355
362
356 with acceptableerrors(*errors):
363 with acceptableerrors(*errors):
357 self.hg(*command)
364 self.hg(*command)
358
365
366 # Section: Repository management
367 @property
368 def currentrepo(self):
369 return os.path.basename(os.getcwd())
370
371 @rule(
372 target=repos,
373 source=repos,
374 name=reponames,
375 )
376 def clone(self, source, name):
377 if not os.path.exists(os.path.join("..", name)):
378 self.cd("..")
379 self.hg("clone", source, name)
380 self.cd(name)
381 return name
382
383 @rule(
384 target=repos,
385 name=reponames,
386 )
387 def fresh(self, name):
388 if not os.path.exists(os.path.join("..", name)):
389 self.cd("..")
390 self.mkdirp(name)
391 self.cd(name)
392 self.hg("init")
393 return name
394
395 @rule(name=repos)
396 def switch(self, name):
397 self.cd(os.path.join("..", name))
398 assert self.currentrepo == name
399 assert os.path.exists(".hg")
400
401 @rule(target=repos)
402 def origin(self):
403 return "repo1"
404
405 @rule()
406 def pull(self, repo=repos):
407 with acceptableerrors(
408 "repository default not found",
409 "repository is unrelated",
410 ):
411 self.hg("pull")
412
413 @rule(newbranch=st.booleans())
414 def push(self, newbranch):
415 with acceptableerrors(
416 "default repository not configured",
417 "no changes found",
418 ):
419 if newbranch:
420 self.hg("push", "--new-branch")
421 else:
422 with acceptableerrors(
423 "creates new branches"
424 ):
425 self.hg("push")
426
359 # Section: Simple side effect free "check" operations
427 # Section: Simple side effect free "check" operations
360 @rule()
428 @rule()
361 def log(self):
429 def log(self):
362 self.hg("log")
430 self.hg("log")
363
431
364 @rule()
432 @rule()
365 def verify(self):
433 def verify(self):
366 self.hg("verify")
434 self.hg("verify")
367
435
368 @rule()
436 @rule()
369 def diff(self):
437 def diff(self):
370 self.hg("diff", "--nodates")
438 self.hg("diff", "--nodates")
371
439
372 @rule()
440 @rule()
373 def status(self):
441 def status(self):
374 self.hg("status")
442 self.hg("status")
375
443
376 @rule()
444 @rule()
377 def export(self):
445 def export(self):
378 self.hg("export")
446 self.hg("export")
379
447
380 # Section: Branch management
448 # Section: Branch management
381 @rule()
449 @rule()
382 def checkbranch(self):
450 def checkbranch(self):
383 self.hg("branch")
451 self.hg("branch")
384
452
385 @rule(branch=branches)
453 @rule(branch=branches)
386 def switchbranch(self, branch):
454 def switchbranch(self, branch):
387 with acceptableerrors(
455 with acceptableerrors(
388 'cannot use an integer as a name',
456 'cannot use an integer as a name',
389 'cannot be used in a name',
457 'cannot be used in a name',
390 'a branch of the same name already exists',
458 'a branch of the same name already exists',
391 'is reserved',
459 'is reserved',
392 ):
460 ):
393 self.hg("branch", "--", branch)
461 self.hg("branch", "--", branch)
394
462
395 @rule(branch=branches, clean=st.booleans())
463 @rule(branch=branches, clean=st.booleans())
396 def update(self, branch, clean):
464 def update(self, branch, clean):
397 with acceptableerrors(
465 with acceptableerrors(
398 'unknown revision',
466 'unknown revision',
399 'parse error',
467 'parse error',
400 ):
468 ):
401 if clean:
469 if clean:
402 self.hg("update", "-C", "--", branch)
470 self.hg("update", "-C", "--", branch)
403 else:
471 else:
404 self.hg("update", "--", branch)
472 self.hg("update", "--", branch)
405
473
406 settings.register_profile(
474 settings.register_profile(
407 'default', settings(
475 'default', settings(
408 timeout=300,
476 timeout=300,
409 stateful_step_count=50,
477 stateful_step_count=50,
410 max_examples=10,
478 max_examples=10,
411 )
479 )
412 )
480 )
413
481
414 settings.register_profile(
482 settings.register_profile(
415 'fast', settings(
483 'fast', settings(
416 timeout=10,
484 timeout=10,
417 stateful_step_count=20,
485 stateful_step_count=20,
418 max_examples=5,
486 max_examples=5,
419 min_satisfying_examples=1,
487 min_satisfying_examples=1,
420 max_shrinks=0,
488 max_shrinks=0,
421 )
489 )
422 )
490 )
423
491
424 settings.load_profile(os.getenv('HYPOTHESIS_PROFILE', 'default'))
492 settings.load_profile(os.getenv('HYPOTHESIS_PROFILE', 'default'))
425
493
426 verifyingtest = verifyingstatemachine.TestCase
494 verifyingtest = verifyingstatemachine.TestCase
427
495
428 verifyingtest.settings = settings.default
496 verifyingtest.settings = settings.default
429
497
430 if __name__ == '__main__':
498 if __name__ == '__main__':
431 try:
499 try:
432 silenttestrunner.main(__name__)
500 silenttestrunner.main(__name__)
433 finally:
501 finally:
434 # So as to prevent proliferation of useless test files, if we never
502 # So as to prevent proliferation of useless test files, if we never
435 # actually wrote a failing test we clean up after ourselves and delete
503 # actually wrote a failing test we clean up after ourselves and delete
436 # the file for doing so that we owned.
504 # the file for doing so that we owned.
437 if os.path.exists(savefile) and os.path.getsize(savefile) == 0:
505 if os.path.exists(savefile) and os.path.getsize(savefile) == 0:
438 os.unlink(savefile)
506 os.unlink(savefile)
General Comments 0
You need to be logged in to leave comments. Login now