##// END OF EJS Templates
util: always use subprocess
Martin Geisler -
r8280:0b02d98d default
parent child Browse files
Show More
@@ -1,1493 +1,1485
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2, incorporated herein by reference.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import cStringIO, errno, re, shutil, sys, tempfile, traceback, error
18 18 import os, stat, threading, time, calendar, glob, osutil, random
19 19 import imp
20 20
21 21 # Python compatibility
22 22
23 23 _md5 = None
24 24 def md5(s):
25 25 global _md5
26 26 if _md5 is None:
27 27 try:
28 28 import hashlib
29 29 _md5 = hashlib.md5
30 30 except ImportError:
31 31 import md5
32 32 _md5 = md5.md5
33 33 return _md5(s)
34 34
35 35 _sha1 = None
36 36 def sha1(s):
37 37 global _sha1
38 38 if _sha1 is None:
39 39 try:
40 40 import hashlib
41 41 _sha1 = hashlib.sha1
42 42 except ImportError:
43 43 import sha
44 44 _sha1 = sha.sha
45 45 return _sha1(s)
46 46
47 try:
48 import subprocess
49 subprocess.Popen # trigger ImportError early
50 closefds = os.name == 'posix'
51 def popen2(cmd, mode='t', bufsize=-1):
52 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
53 close_fds=closefds,
54 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
55 return p.stdin, p.stdout
56 def popen3(cmd, mode='t', bufsize=-1):
57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 close_fds=closefds,
59 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
60 stderr=subprocess.PIPE)
61 return p.stdin, p.stdout, p.stderr
62 def Popen3(cmd, capturestderr=False, bufsize=-1):
63 stderr = capturestderr and subprocess.PIPE or None
64 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
65 close_fds=closefds,
66 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
67 stderr=stderr)
68 p.fromchild = p.stdout
69 p.tochild = p.stdin
70 p.childerr = p.stderr
71 return p
72 except ImportError:
73 subprocess = None
74 from popen2 import Popen3
75 popen2 = os.popen2
76 popen3 = os.popen3
77
47 import subprocess
48 closefds = os.name == 'posix'
49 def popen2(cmd, mode='t', bufsize=-1):
50 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
51 close_fds=closefds,
52 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
53 return p.stdin, p.stdout
54 def popen3(cmd, mode='t', bufsize=-1):
55 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
56 close_fds=closefds,
57 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
58 stderr=subprocess.PIPE)
59 return p.stdin, p.stdout, p.stderr
60 def Popen3(cmd, capturestderr=False, bufsize=-1):
61 stderr = capturestderr and subprocess.PIPE or None
62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
63 close_fds=closefds,
64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 stderr=stderr)
66 p.fromchild = p.stdout
67 p.tochild = p.stdin
68 p.childerr = p.stderr
69 return p
78 70
79 71 def version():
80 72 """Return version information if available."""
81 73 try:
82 74 import __version__
83 75 return __version__.version
84 76 except ImportError:
85 77 return 'unknown'
86 78
87 79 # used by parsedate
88 80 defaultdateformats = (
89 81 '%Y-%m-%d %H:%M:%S',
90 82 '%Y-%m-%d %I:%M:%S%p',
91 83 '%Y-%m-%d %H:%M',
92 84 '%Y-%m-%d %I:%M%p',
93 85 '%Y-%m-%d',
94 86 '%m-%d',
95 87 '%m/%d',
96 88 '%m/%d/%y',
97 89 '%m/%d/%Y',
98 90 '%a %b %d %H:%M:%S %Y',
99 91 '%a %b %d %I:%M:%S%p %Y',
100 92 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
101 93 '%b %d %H:%M:%S %Y',
102 94 '%b %d %I:%M:%S%p %Y',
103 95 '%b %d %H:%M:%S',
104 96 '%b %d %I:%M:%S%p',
105 97 '%b %d %H:%M',
106 98 '%b %d %I:%M%p',
107 99 '%b %d %Y',
108 100 '%b %d',
109 101 '%H:%M:%S',
110 102 '%I:%M:%SP',
111 103 '%H:%M',
112 104 '%I:%M%p',
113 105 )
114 106
115 107 extendeddateformats = defaultdateformats + (
116 108 "%Y",
117 109 "%Y-%m",
118 110 "%b",
119 111 "%b %Y",
120 112 )
121 113
122 114 def cachefunc(func):
123 115 '''cache the result of function calls'''
124 116 # XXX doesn't handle keywords args
125 117 cache = {}
126 118 if func.func_code.co_argcount == 1:
127 119 # we gain a small amount of time because
128 120 # we don't need to pack/unpack the list
129 121 def f(arg):
130 122 if arg not in cache:
131 123 cache[arg] = func(arg)
132 124 return cache[arg]
133 125 else:
134 126 def f(*args):
135 127 if args not in cache:
136 128 cache[args] = func(*args)
137 129 return cache[args]
138 130
139 131 return f
140 132
141 133 class propertycache(object):
142 134 def __init__(self, func):
143 135 self.func = func
144 136 self.name = func.__name__
145 137 def __get__(self, obj, type=None):
146 138 result = self.func(obj)
147 139 setattr(obj, self.name, result)
148 140 return result
149 141
150 142 def pipefilter(s, cmd):
151 143 '''filter string S through command CMD, returning its output'''
152 144 (pin, pout) = popen2(cmd, 'b')
153 145 def writer():
154 146 try:
155 147 pin.write(s)
156 148 pin.close()
157 149 except IOError, inst:
158 150 if inst.errno != errno.EPIPE:
159 151 raise
160 152
161 153 # we should use select instead on UNIX, but this will work on most
162 154 # systems, including Windows
163 155 w = threading.Thread(target=writer)
164 156 w.start()
165 157 f = pout.read()
166 158 pout.close()
167 159 w.join()
168 160 return f
169 161
170 162 def tempfilter(s, cmd):
171 163 '''filter string S through a pair of temporary files with CMD.
172 164 CMD is used as a template to create the real command to be run,
173 165 with the strings INFILE and OUTFILE replaced by the real names of
174 166 the temporary files generated.'''
175 167 inname, outname = None, None
176 168 try:
177 169 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
178 170 fp = os.fdopen(infd, 'wb')
179 171 fp.write(s)
180 172 fp.close()
181 173 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
182 174 os.close(outfd)
183 175 cmd = cmd.replace('INFILE', inname)
184 176 cmd = cmd.replace('OUTFILE', outname)
185 177 code = os.system(cmd)
186 178 if sys.platform == 'OpenVMS' and code & 1:
187 179 code = 0
188 180 if code: raise Abort(_("command '%s' failed: %s") %
189 181 (cmd, explain_exit(code)))
190 182 return open(outname, 'rb').read()
191 183 finally:
192 184 try:
193 185 if inname: os.unlink(inname)
194 186 except: pass
195 187 try:
196 188 if outname: os.unlink(outname)
197 189 except: pass
198 190
199 191 filtertable = {
200 192 'tempfile:': tempfilter,
201 193 'pipe:': pipefilter,
202 194 }
203 195
204 196 def filter(s, cmd):
205 197 "filter a string through a command that transforms its input to its output"
206 198 for name, fn in filtertable.iteritems():
207 199 if cmd.startswith(name):
208 200 return fn(s, cmd[len(name):].lstrip())
209 201 return pipefilter(s, cmd)
210 202
211 203 def binary(s):
212 204 """return true if a string is binary data"""
213 205 return bool(s and '\0' in s)
214 206
215 207 def increasingchunks(source, min=1024, max=65536):
216 208 '''return no less than min bytes per chunk while data remains,
217 209 doubling min after each chunk until it reaches max'''
218 210 def log2(x):
219 211 if not x:
220 212 return 0
221 213 i = 0
222 214 while x:
223 215 x >>= 1
224 216 i += 1
225 217 return i - 1
226 218
227 219 buf = []
228 220 blen = 0
229 221 for chunk in source:
230 222 buf.append(chunk)
231 223 blen += len(chunk)
232 224 if blen >= min:
233 225 if min < max:
234 226 min = min << 1
235 227 nmin = 1 << log2(blen)
236 228 if nmin > min:
237 229 min = nmin
238 230 if min > max:
239 231 min = max
240 232 yield ''.join(buf)
241 233 blen = 0
242 234 buf = []
243 235 if buf:
244 236 yield ''.join(buf)
245 237
246 238 Abort = error.Abort
247 239
248 240 def always(fn): return True
249 241 def never(fn): return False
250 242
251 243 def patkind(name, default):
252 244 """Split a string into an optional pattern kind prefix and the
253 245 actual pattern."""
254 246 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
255 247 if name.startswith(prefix + ':'): return name.split(':', 1)
256 248 return default, name
257 249
258 250 def globre(pat, head='^', tail='$'):
259 251 "convert a glob pattern into a regexp"
260 252 i, n = 0, len(pat)
261 253 res = ''
262 254 group = 0
263 255 def peek(): return i < n and pat[i]
264 256 while i < n:
265 257 c = pat[i]
266 258 i = i+1
267 259 if c == '*':
268 260 if peek() == '*':
269 261 i += 1
270 262 res += '.*'
271 263 else:
272 264 res += '[^/]*'
273 265 elif c == '?':
274 266 res += '.'
275 267 elif c == '[':
276 268 j = i
277 269 if j < n and pat[j] in '!]':
278 270 j += 1
279 271 while j < n and pat[j] != ']':
280 272 j += 1
281 273 if j >= n:
282 274 res += '\\['
283 275 else:
284 276 stuff = pat[i:j].replace('\\','\\\\')
285 277 i = j + 1
286 278 if stuff[0] == '!':
287 279 stuff = '^' + stuff[1:]
288 280 elif stuff[0] == '^':
289 281 stuff = '\\' + stuff
290 282 res = '%s[%s]' % (res, stuff)
291 283 elif c == '{':
292 284 group += 1
293 285 res += '(?:'
294 286 elif c == '}' and group:
295 287 res += ')'
296 288 group -= 1
297 289 elif c == ',' and group:
298 290 res += '|'
299 291 elif c == '\\':
300 292 p = peek()
301 293 if p:
302 294 i += 1
303 295 res += re.escape(p)
304 296 else:
305 297 res += re.escape(c)
306 298 else:
307 299 res += re.escape(c)
308 300 return head + res + tail
309 301
310 302 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
311 303
312 304 def pathto(root, n1, n2):
313 305 '''return the relative path from one place to another.
314 306 root should use os.sep to separate directories
315 307 n1 should use os.sep to separate directories
316 308 n2 should use "/" to separate directories
317 309 returns an os.sep-separated path.
318 310
319 311 If n1 is a relative path, it's assumed it's
320 312 relative to root.
321 313 n2 should always be relative to root.
322 314 '''
323 315 if not n1: return localpath(n2)
324 316 if os.path.isabs(n1):
325 317 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
326 318 return os.path.join(root, localpath(n2))
327 319 n2 = '/'.join((pconvert(root), n2))
328 320 a, b = splitpath(n1), n2.split('/')
329 321 a.reverse()
330 322 b.reverse()
331 323 while a and b and a[-1] == b[-1]:
332 324 a.pop()
333 325 b.pop()
334 326 b.reverse()
335 327 return os.sep.join((['..'] * len(a)) + b) or '.'
336 328
337 329 def canonpath(root, cwd, myname):
338 330 """return the canonical path of myname, given cwd and root"""
339 331 if root == os.sep:
340 332 rootsep = os.sep
341 333 elif endswithsep(root):
342 334 rootsep = root
343 335 else:
344 336 rootsep = root + os.sep
345 337 name = myname
346 338 if not os.path.isabs(name):
347 339 name = os.path.join(root, cwd, name)
348 340 name = os.path.normpath(name)
349 341 audit_path = path_auditor(root)
350 342 if name != rootsep and name.startswith(rootsep):
351 343 name = name[len(rootsep):]
352 344 audit_path(name)
353 345 return pconvert(name)
354 346 elif name == root:
355 347 return ''
356 348 else:
357 349 # Determine whether `name' is in the hierarchy at or beneath `root',
358 350 # by iterating name=dirname(name) until that causes no change (can't
359 351 # check name == '/', because that doesn't work on windows). For each
360 352 # `name', compare dev/inode numbers. If they match, the list `rel'
361 353 # holds the reversed list of components making up the relative file
362 354 # name we want.
363 355 root_st = os.stat(root)
364 356 rel = []
365 357 while True:
366 358 try:
367 359 name_st = os.stat(name)
368 360 except OSError:
369 361 break
370 362 if samestat(name_st, root_st):
371 363 if not rel:
372 364 # name was actually the same as root (maybe a symlink)
373 365 return ''
374 366 rel.reverse()
375 367 name = os.path.join(*rel)
376 368 audit_path(name)
377 369 return pconvert(name)
378 370 dirname, basename = os.path.split(name)
379 371 rel.append(basename)
380 372 if dirname == name:
381 373 break
382 374 name = dirname
383 375
384 376 raise Abort('%s not under root' % myname)
385 377
386 378 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
387 379 """build a function to match a set of file patterns
388 380
389 381 arguments:
390 382 canonroot - the canonical root of the tree you're matching against
391 383 cwd - the current working directory, if relevant
392 384 names - patterns to find
393 385 inc - patterns to include
394 386 exc - patterns to exclude
395 387 dflt_pat - if a pattern in names has no explicit type, assume this one
396 388 src - where these patterns came from (e.g. .hgignore)
397 389
398 390 a pattern is one of:
399 391 'glob:<glob>' - a glob relative to cwd
400 392 're:<regexp>' - a regular expression
401 393 'path:<path>' - a path relative to canonroot
402 394 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
403 395 'relpath:<path>' - a path relative to cwd
404 396 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
405 397 '<something>' - one of the cases above, selected by the dflt_pat argument
406 398
407 399 returns:
408 400 a 3-tuple containing
409 401 - list of roots (places where one should start a recursive walk of the fs);
410 402 this often matches the explicit non-pattern names passed in, but also
411 403 includes the initial part of glob: patterns that has no glob characters
412 404 - a bool match(filename) function
413 405 - a bool indicating if any patterns were passed in
414 406 """
415 407
416 408 # a common case: no patterns at all
417 409 if not names and not inc and not exc:
418 410 return [], always, False
419 411
420 412 def contains_glob(name):
421 413 for c in name:
422 414 if c in _globchars: return True
423 415 return False
424 416
425 417 def regex(kind, name, tail):
426 418 '''convert a pattern into a regular expression'''
427 419 if not name:
428 420 return ''
429 421 if kind == 're':
430 422 return name
431 423 elif kind == 'path':
432 424 return '^' + re.escape(name) + '(?:/|$)'
433 425 elif kind == 'relglob':
434 426 return globre(name, '(?:|.*/)', tail)
435 427 elif kind == 'relpath':
436 428 return re.escape(name) + '(?:/|$)'
437 429 elif kind == 'relre':
438 430 if name.startswith('^'):
439 431 return name
440 432 return '.*' + name
441 433 return globre(name, '', tail)
442 434
443 435 def matchfn(pats, tail):
444 436 """build a matching function from a set of patterns"""
445 437 if not pats:
446 438 return
447 439 try:
448 440 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
449 441 if len(pat) > 20000:
450 442 raise OverflowError()
451 443 return re.compile(pat).match
452 444 except OverflowError:
453 445 # We're using a Python with a tiny regex engine and we
454 446 # made it explode, so we'll divide the pattern list in two
455 447 # until it works
456 448 l = len(pats)
457 449 if l < 2:
458 450 raise
459 451 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
460 452 return lambda s: a(s) or b(s)
461 453 except re.error:
462 454 for k, p in pats:
463 455 try:
464 456 re.compile('(?:%s)' % regex(k, p, tail))
465 457 except re.error:
466 458 if src:
467 459 raise Abort("%s: invalid pattern (%s): %s" %
468 460 (src, k, p))
469 461 else:
470 462 raise Abort("invalid pattern (%s): %s" % (k, p))
471 463 raise Abort("invalid pattern")
472 464
473 465 def globprefix(pat):
474 466 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
475 467 root = []
476 468 for p in pat.split('/'):
477 469 if contains_glob(p): break
478 470 root.append(p)
479 471 return '/'.join(root) or '.'
480 472
481 473 def normalizepats(names, default):
482 474 pats = []
483 475 roots = []
484 476 anypats = False
485 477 for kind, name in [patkind(p, default) for p in names]:
486 478 if kind in ('glob', 'relpath'):
487 479 name = canonpath(canonroot, cwd, name)
488 480 elif kind in ('relglob', 'path'):
489 481 name = normpath(name)
490 482
491 483 pats.append((kind, name))
492 484
493 485 if kind in ('glob', 're', 'relglob', 'relre'):
494 486 anypats = True
495 487
496 488 if kind == 'glob':
497 489 root = globprefix(name)
498 490 roots.append(root)
499 491 elif kind in ('relpath', 'path'):
500 492 roots.append(name or '.')
501 493 elif kind == 'relglob':
502 494 roots.append('.')
503 495 return roots, pats, anypats
504 496
505 497 roots, pats, anypats = normalizepats(names, dflt_pat)
506 498
507 499 patmatch = matchfn(pats, '$') or always
508 500 incmatch = always
509 501 if inc:
510 502 dummy, inckinds, dummy = normalizepats(inc, 'glob')
511 503 incmatch = matchfn(inckinds, '(?:/|$)')
512 504 excmatch = never
513 505 if exc:
514 506 dummy, exckinds, dummy = normalizepats(exc, 'glob')
515 507 excmatch = matchfn(exckinds, '(?:/|$)')
516 508
517 509 if not names and inc and not exc:
518 510 # common case: hgignore patterns
519 511 match = incmatch
520 512 else:
521 513 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
522 514
523 515 return (roots, match, (inc or exc or anypats) and True)
524 516
525 517 _hgexecutable = None
526 518
527 519 def main_is_frozen():
528 520 """return True if we are a frozen executable.
529 521
530 522 The code supports py2exe (most common, Windows only) and tools/freeze
531 523 (portable, not much used).
532 524 """
533 525 return (hasattr(sys, "frozen") or # new py2exe
534 526 hasattr(sys, "importers") or # old py2exe
535 527 imp.is_frozen("__main__")) # tools/freeze
536 528
537 529 def hgexecutable():
538 530 """return location of the 'hg' executable.
539 531
540 532 Defaults to $HG or 'hg' in the search path.
541 533 """
542 534 if _hgexecutable is None:
543 535 hg = os.environ.get('HG')
544 536 if hg:
545 537 set_hgexecutable(hg)
546 538 elif main_is_frozen():
547 539 set_hgexecutable(sys.executable)
548 540 else:
549 541 set_hgexecutable(find_exe('hg') or 'hg')
550 542 return _hgexecutable
551 543
552 544 def set_hgexecutable(path):
553 545 """set location of the 'hg' executable"""
554 546 global _hgexecutable
555 547 _hgexecutable = path
556 548
557 549 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
558 550 '''enhanced shell command execution.
559 551 run with environment maybe modified, maybe in different dir.
560 552
561 553 if command fails and onerr is None, return status. if ui object,
562 554 print error message and return status, else raise onerr object as
563 555 exception.'''
564 556 def py2shell(val):
565 557 'convert python object into string that is useful to shell'
566 558 if val in (None, False):
567 559 return '0'
568 560 if val == True:
569 561 return '1'
570 562 return str(val)
571 563 oldenv = {}
572 564 for k in environ:
573 565 oldenv[k] = os.environ.get(k)
574 566 if cwd is not None:
575 567 oldcwd = os.getcwd()
576 568 origcmd = cmd
577 569 if os.name == 'nt':
578 570 cmd = '"%s"' % cmd
579 571 try:
580 572 for k, v in environ.iteritems():
581 573 os.environ[k] = py2shell(v)
582 574 os.environ['HG'] = hgexecutable()
583 575 if cwd is not None and oldcwd != cwd:
584 576 os.chdir(cwd)
585 577 rc = os.system(cmd)
586 578 if sys.platform == 'OpenVMS' and rc & 1:
587 579 rc = 0
588 580 if rc and onerr:
589 581 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
590 582 explain_exit(rc)[0])
591 583 if errprefix:
592 584 errmsg = '%s: %s' % (errprefix, errmsg)
593 585 try:
594 586 onerr.warn(errmsg + '\n')
595 587 except AttributeError:
596 588 raise onerr(errmsg)
597 589 return rc
598 590 finally:
599 591 for k, v in oldenv.iteritems():
600 592 if v is None:
601 593 del os.environ[k]
602 594 else:
603 595 os.environ[k] = v
604 596 if cwd is not None and oldcwd != cwd:
605 597 os.chdir(oldcwd)
606 598
607 599 def checksignature(func):
608 600 '''wrap a function with code to check for calling errors'''
609 601 def check(*args, **kwargs):
610 602 try:
611 603 return func(*args, **kwargs)
612 604 except TypeError:
613 605 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
614 606 raise error.SignatureError
615 607 raise
616 608
617 609 return check
618 610
619 611 # os.path.lexists is not available on python2.3
620 612 def lexists(filename):
621 613 "test whether a file with this name exists. does not follow symlinks"
622 614 try:
623 615 os.lstat(filename)
624 616 except:
625 617 return False
626 618 return True
627 619
628 620 def rename(src, dst):
629 621 """forcibly rename a file"""
630 622 try:
631 623 os.rename(src, dst)
632 624 except OSError, err: # FIXME: check err (EEXIST ?)
633 625
634 626 # On windows, rename to existing file is not allowed, so we
635 627 # must delete destination first. But if a file is open, unlink
636 628 # schedules it for delete but does not delete it. Rename
637 629 # happens immediately even for open files, so we rename
638 630 # destination to a temporary name, then delete that. Then
639 631 # rename is safe to do.
640 632 # The temporary name is chosen at random to avoid the situation
641 633 # where a file is left lying around from a previous aborted run.
642 634 # The usual race condition this introduces can't be avoided as
643 635 # we need the name to rename into, and not the file itself. Due
644 636 # to the nature of the operation however, any races will at worst
645 637 # lead to the rename failing and the current operation aborting.
646 638
647 639 def tempname(prefix):
648 640 for tries in xrange(10):
649 641 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
650 642 if not os.path.exists(temp):
651 643 return temp
652 644 raise IOError, (errno.EEXIST, "No usable temporary filename found")
653 645
654 646 temp = tempname(dst)
655 647 os.rename(dst, temp)
656 648 os.unlink(temp)
657 649 os.rename(src, dst)
658 650
659 651 def unlink(f):
660 652 """unlink and remove the directory if it is empty"""
661 653 os.unlink(f)
662 654 # try removing directories that might now be empty
663 655 try:
664 656 os.removedirs(os.path.dirname(f))
665 657 except OSError:
666 658 pass
667 659
668 660 def copyfile(src, dest):
669 661 "copy a file, preserving mode and atime/mtime"
670 662 if os.path.islink(src):
671 663 try:
672 664 os.unlink(dest)
673 665 except:
674 666 pass
675 667 os.symlink(os.readlink(src), dest)
676 668 else:
677 669 try:
678 670 shutil.copyfile(src, dest)
679 671 shutil.copystat(src, dest)
680 672 except shutil.Error, inst:
681 673 raise Abort(str(inst))
682 674
683 675 def copyfiles(src, dst, hardlink=None):
684 676 """Copy a directory tree using hardlinks if possible"""
685 677
686 678 if hardlink is None:
687 679 hardlink = (os.stat(src).st_dev ==
688 680 os.stat(os.path.dirname(dst)).st_dev)
689 681
690 682 if os.path.isdir(src):
691 683 os.mkdir(dst)
692 684 for name, kind in osutil.listdir(src):
693 685 srcname = os.path.join(src, name)
694 686 dstname = os.path.join(dst, name)
695 687 copyfiles(srcname, dstname, hardlink)
696 688 else:
697 689 if hardlink:
698 690 try:
699 691 os_link(src, dst)
700 692 except (IOError, OSError):
701 693 hardlink = False
702 694 shutil.copy(src, dst)
703 695 else:
704 696 shutil.copy(src, dst)
705 697
706 698 class path_auditor(object):
707 699 '''ensure that a filesystem path contains no banned components.
708 700 the following properties of a path are checked:
709 701
710 702 - under top-level .hg
711 703 - starts at the root of a windows drive
712 704 - contains ".."
713 705 - traverses a symlink (e.g. a/symlink_here/b)
714 706 - inside a nested repository'''
715 707
716 708 def __init__(self, root):
717 709 self.audited = set()
718 710 self.auditeddir = set()
719 711 self.root = root
720 712
721 713 def __call__(self, path):
722 714 if path in self.audited:
723 715 return
724 716 normpath = os.path.normcase(path)
725 717 parts = splitpath(normpath)
726 718 if (os.path.splitdrive(path)[0]
727 719 or parts[0].lower() in ('.hg', '.hg.', '')
728 720 or os.pardir in parts):
729 721 raise Abort(_("path contains illegal component: %s") % path)
730 722 if '.hg' in path.lower():
731 723 lparts = [p.lower() for p in parts]
732 724 for p in '.hg', '.hg.':
733 725 if p in lparts[1:]:
734 726 pos = lparts.index(p)
735 727 base = os.path.join(*parts[:pos])
736 728 raise Abort(_('path %r is inside repo %r') % (path, base))
737 729 def check(prefix):
738 730 curpath = os.path.join(self.root, prefix)
739 731 try:
740 732 st = os.lstat(curpath)
741 733 except OSError, err:
742 734 # EINVAL can be raised as invalid path syntax under win32.
743 735 # They must be ignored for patterns can be checked too.
744 736 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
745 737 raise
746 738 else:
747 739 if stat.S_ISLNK(st.st_mode):
748 740 raise Abort(_('path %r traverses symbolic link %r') %
749 741 (path, prefix))
750 742 elif (stat.S_ISDIR(st.st_mode) and
751 743 os.path.isdir(os.path.join(curpath, '.hg'))):
752 744 raise Abort(_('path %r is inside repo %r') %
753 745 (path, prefix))
754 746 parts.pop()
755 747 prefixes = []
756 748 for n in range(len(parts)):
757 749 prefix = os.sep.join(parts)
758 750 if prefix in self.auditeddir:
759 751 break
760 752 check(prefix)
761 753 prefixes.append(prefix)
762 754 parts.pop()
763 755
764 756 self.audited.add(path)
765 757 # only add prefixes to the cache after checking everything: we don't
766 758 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
767 759 self.auditeddir.update(prefixes)
768 760
769 761 def nlinks(pathname):
770 762 """Return number of hardlinks for the given file."""
771 763 return os.lstat(pathname).st_nlink
772 764
773 765 if hasattr(os, 'link'):
774 766 os_link = os.link
775 767 else:
776 768 def os_link(src, dst):
777 769 raise OSError(0, _("Hardlinks not supported"))
778 770
779 771 def lookup_reg(key, name=None, scope=None):
780 772 return None
781 773
782 774 if os.name == 'nt':
783 775 from windows import *
784 776 def expand_glob(pats):
785 777 '''On Windows, expand the implicit globs in a list of patterns'''
786 778 ret = []
787 779 for p in pats:
788 780 kind, name = patkind(p, None)
789 781 if kind is None:
790 782 globbed = glob.glob(name)
791 783 if globbed:
792 784 ret.extend(globbed)
793 785 continue
794 786 # if we couldn't expand the glob, just keep it around
795 787 ret.append(p)
796 788 return ret
797 789 else:
798 790 from posix import *
799 791
800 792 def makelock(info, pathname):
801 793 try:
802 794 return os.symlink(info, pathname)
803 795 except OSError, why:
804 796 if why.errno == errno.EEXIST:
805 797 raise
806 798 except AttributeError: # no symlink in os
807 799 pass
808 800
809 801 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
810 802 os.write(ld, info)
811 803 os.close(ld)
812 804
813 805 def readlock(pathname):
814 806 try:
815 807 return os.readlink(pathname)
816 808 except OSError, why:
817 809 if why.errno not in (errno.EINVAL, errno.ENOSYS):
818 810 raise
819 811 except AttributeError: # no symlink in os
820 812 pass
821 813 return posixfile(pathname).read()
822 814
823 815 def fstat(fp):
824 816 '''stat file object that may not have fileno method.'''
825 817 try:
826 818 return os.fstat(fp.fileno())
827 819 except AttributeError:
828 820 return os.stat(fp.name)
829 821
830 822 # File system features
831 823
832 824 def checkcase(path):
833 825 """
834 826 Check whether the given path is on a case-sensitive filesystem
835 827
836 828 Requires a path (like /foo/.hg) ending with a foldable final
837 829 directory component.
838 830 """
839 831 s1 = os.stat(path)
840 832 d, b = os.path.split(path)
841 833 p2 = os.path.join(d, b.upper())
842 834 if path == p2:
843 835 p2 = os.path.join(d, b.lower())
844 836 try:
845 837 s2 = os.stat(p2)
846 838 if s2 == s1:
847 839 return False
848 840 return True
849 841 except:
850 842 return True
851 843
852 844 _fspathcache = {}
853 845 def fspath(name, root):
854 846 '''Get name in the case stored in the filesystem
855 847
856 848 The name is either relative to root, or it is an absolute path starting
857 849 with root. Note that this function is unnecessary, and should not be
858 850 called, for case-sensitive filesystems (simply because it's expensive).
859 851 '''
860 852 # If name is absolute, make it relative
861 853 if name.lower().startswith(root.lower()):
862 854 l = len(root)
863 855 if name[l] == os.sep or name[l] == os.altsep:
864 856 l = l + 1
865 857 name = name[l:]
866 858
867 859 if not os.path.exists(os.path.join(root, name)):
868 860 return None
869 861
870 862 seps = os.sep
871 863 if os.altsep:
872 864 seps = seps + os.altsep
873 865 # Protect backslashes. This gets silly very quickly.
874 866 seps.replace('\\','\\\\')
875 867 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
876 868 dir = os.path.normcase(os.path.normpath(root))
877 869 result = []
878 870 for part, sep in pattern.findall(name):
879 871 if sep:
880 872 result.append(sep)
881 873 continue
882 874
883 875 if dir not in _fspathcache:
884 876 _fspathcache[dir] = os.listdir(dir)
885 877 contents = _fspathcache[dir]
886 878
887 879 lpart = part.lower()
888 880 for n in contents:
889 881 if n.lower() == lpart:
890 882 result.append(n)
891 883 break
892 884 else:
893 885 # Cannot happen, as the file exists!
894 886 result.append(part)
895 887 dir = os.path.join(dir, lpart)
896 888
897 889 return ''.join(result)
898 890
899 891 def checkexec(path):
900 892 """
901 893 Check whether the given path is on a filesystem with UNIX-like exec flags
902 894
903 895 Requires a directory (like /foo/.hg)
904 896 """
905 897
906 898 # VFAT on some Linux versions can flip mode but it doesn't persist
907 899 # a FS remount. Frequently we can detect it if files are created
908 900 # with exec bit on.
909 901
910 902 try:
911 903 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
912 904 fh, fn = tempfile.mkstemp("", "", path)
913 905 try:
914 906 os.close(fh)
915 907 m = os.stat(fn).st_mode & 0777
916 908 new_file_has_exec = m & EXECFLAGS
917 909 os.chmod(fn, m ^ EXECFLAGS)
918 910 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
919 911 finally:
920 912 os.unlink(fn)
921 913 except (IOError, OSError):
922 914 # we don't care, the user probably won't be able to commit anyway
923 915 return False
924 916 return not (new_file_has_exec or exec_flags_cannot_flip)
925 917
926 918 def checklink(path):
927 919 """check whether the given path is on a symlink-capable filesystem"""
928 920 # mktemp is not racy because symlink creation will fail if the
929 921 # file already exists
930 922 name = tempfile.mktemp(dir=path)
931 923 try:
932 924 os.symlink(".", name)
933 925 os.unlink(name)
934 926 return True
935 927 except (OSError, AttributeError):
936 928 return False
937 929
938 930 def needbinarypatch():
939 931 """return True if patches should be applied in binary mode by default."""
940 932 return os.name == 'nt'
941 933
942 934 def endswithsep(path):
943 935 '''Check path ends with os.sep or os.altsep.'''
944 936 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
945 937
946 938 def splitpath(path):
947 939 '''Split path by os.sep.
948 940 Note that this function does not use os.altsep because this is
949 941 an alternative of simple "xxx.split(os.sep)".
950 942 It is recommended to use os.path.normpath() before using this
951 943 function if need.'''
952 944 return path.split(os.sep)
953 945
954 946 def gui():
955 947 '''Are we running in a GUI?'''
956 948 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
957 949
958 950 def mktempcopy(name, emptyok=False, createmode=None):
959 951 """Create a temporary file with the same contents from name
960 952
961 953 The permission bits are copied from the original file.
962 954
963 955 If the temporary file is going to be truncated immediately, you
964 956 can use emptyok=True as an optimization.
965 957
966 958 Returns the name of the temporary file.
967 959 """
968 960 d, fn = os.path.split(name)
969 961 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
970 962 os.close(fd)
971 963 # Temporary files are created with mode 0600, which is usually not
972 964 # what we want. If the original file already exists, just copy
973 965 # its mode. Otherwise, manually obey umask.
974 966 try:
975 967 st_mode = os.lstat(name).st_mode & 0777
976 968 except OSError, inst:
977 969 if inst.errno != errno.ENOENT:
978 970 raise
979 971 st_mode = createmode
980 972 if st_mode is None:
981 973 st_mode = ~umask
982 974 st_mode &= 0666
983 975 os.chmod(temp, st_mode)
984 976 if emptyok:
985 977 return temp
986 978 try:
987 979 try:
988 980 ifp = posixfile(name, "rb")
989 981 except IOError, inst:
990 982 if inst.errno == errno.ENOENT:
991 983 return temp
992 984 if not getattr(inst, 'filename', None):
993 985 inst.filename = name
994 986 raise
995 987 ofp = posixfile(temp, "wb")
996 988 for chunk in filechunkiter(ifp):
997 989 ofp.write(chunk)
998 990 ifp.close()
999 991 ofp.close()
1000 992 except:
1001 993 try: os.unlink(temp)
1002 994 except: pass
1003 995 raise
1004 996 return temp
1005 997
1006 998 class atomictempfile(posixfile):
1007 999 """file-like object that atomically updates a file
1008 1000
1009 1001 All writes will be redirected to a temporary copy of the original
1010 1002 file. When rename is called, the copy is renamed to the original
1011 1003 name, making the changes visible.
1012 1004 """
1013 1005 def __init__(self, name, mode, createmode):
1014 1006 self.__name = name
1015 1007 self.temp = mktempcopy(name, emptyok=('w' in mode),
1016 1008 createmode=createmode)
1017 1009 posixfile.__init__(self, self.temp, mode)
1018 1010
1019 1011 def rename(self):
1020 1012 if not self.closed:
1021 1013 posixfile.close(self)
1022 1014 rename(self.temp, localpath(self.__name))
1023 1015
1024 1016 def __del__(self):
1025 1017 if not self.closed:
1026 1018 try:
1027 1019 os.unlink(self.temp)
1028 1020 except: pass
1029 1021 posixfile.close(self)
1030 1022
1031 1023 def makedirs(name, mode=None):
1032 1024 """recursive directory creation with parent mode inheritance"""
1033 1025 try:
1034 1026 os.mkdir(name)
1035 1027 if mode is not None:
1036 1028 os.chmod(name, mode)
1037 1029 return
1038 1030 except OSError, err:
1039 1031 if err.errno == errno.EEXIST:
1040 1032 return
1041 1033 if err.errno != errno.ENOENT:
1042 1034 raise
1043 1035 parent = os.path.abspath(os.path.dirname(name))
1044 1036 makedirs(parent, mode)
1045 1037 makedirs(name, mode)
1046 1038
1047 1039 class opener(object):
1048 1040 """Open files relative to a base directory
1049 1041
1050 1042 This class is used to hide the details of COW semantics and
1051 1043 remote file access from higher level code.
1052 1044 """
1053 1045 def __init__(self, base, audit=True):
1054 1046 self.base = base
1055 1047 if audit:
1056 1048 self.audit_path = path_auditor(base)
1057 1049 else:
1058 1050 self.audit_path = always
1059 1051 self.createmode = None
1060 1052
1061 1053 def __getattr__(self, name):
1062 1054 if name == '_can_symlink':
1063 1055 self._can_symlink = checklink(self.base)
1064 1056 return self._can_symlink
1065 1057 raise AttributeError(name)
1066 1058
1067 1059 def _fixfilemode(self, name):
1068 1060 if self.createmode is None:
1069 1061 return
1070 1062 os.chmod(name, self.createmode & 0666)
1071 1063
1072 1064 def __call__(self, path, mode="r", text=False, atomictemp=False):
1073 1065 self.audit_path(path)
1074 1066 f = os.path.join(self.base, path)
1075 1067
1076 1068 if not text and "b" not in mode:
1077 1069 mode += "b" # for that other OS
1078 1070
1079 1071 nlink = -1
1080 1072 if mode not in ("r", "rb"):
1081 1073 try:
1082 1074 nlink = nlinks(f)
1083 1075 except OSError:
1084 1076 nlink = 0
1085 1077 d = os.path.dirname(f)
1086 1078 if not os.path.isdir(d):
1087 1079 makedirs(d, self.createmode)
1088 1080 if atomictemp:
1089 1081 return atomictempfile(f, mode, self.createmode)
1090 1082 if nlink > 1:
1091 1083 rename(mktempcopy(f), f)
1092 1084 fp = posixfile(f, mode)
1093 1085 if nlink == 0:
1094 1086 self._fixfilemode(f)
1095 1087 return fp
1096 1088
1097 1089 def symlink(self, src, dst):
1098 1090 self.audit_path(dst)
1099 1091 linkname = os.path.join(self.base, dst)
1100 1092 try:
1101 1093 os.unlink(linkname)
1102 1094 except OSError:
1103 1095 pass
1104 1096
1105 1097 dirname = os.path.dirname(linkname)
1106 1098 if not os.path.exists(dirname):
1107 1099 makedirs(dirname, self.createmode)
1108 1100
1109 1101 if self._can_symlink:
1110 1102 try:
1111 1103 os.symlink(src, linkname)
1112 1104 except OSError, err:
1113 1105 raise OSError(err.errno, _('could not symlink to %r: %s') %
1114 1106 (src, err.strerror), linkname)
1115 1107 else:
1116 1108 f = self(dst, "w")
1117 1109 f.write(src)
1118 1110 f.close()
1119 1111 self._fixfilemode(dst)
1120 1112
1121 1113 class chunkbuffer(object):
1122 1114 """Allow arbitrary sized chunks of data to be efficiently read from an
1123 1115 iterator over chunks of arbitrary size."""
1124 1116
1125 1117 def __init__(self, in_iter):
1126 1118 """in_iter is the iterator that's iterating over the input chunks.
1127 1119 targetsize is how big a buffer to try to maintain."""
1128 1120 self.iter = iter(in_iter)
1129 1121 self.buf = ''
1130 1122 self.targetsize = 2**16
1131 1123
1132 1124 def read(self, l):
1133 1125 """Read L bytes of data from the iterator of chunks of data.
1134 1126 Returns less than L bytes if the iterator runs dry."""
1135 1127 if l > len(self.buf) and self.iter:
1136 1128 # Clamp to a multiple of self.targetsize
1137 1129 targetsize = max(l, self.targetsize)
1138 1130 collector = cStringIO.StringIO()
1139 1131 collector.write(self.buf)
1140 1132 collected = len(self.buf)
1141 1133 for chunk in self.iter:
1142 1134 collector.write(chunk)
1143 1135 collected += len(chunk)
1144 1136 if collected >= targetsize:
1145 1137 break
1146 1138 if collected < targetsize:
1147 1139 self.iter = False
1148 1140 self.buf = collector.getvalue()
1149 1141 if len(self.buf) == l:
1150 1142 s, self.buf = str(self.buf), ''
1151 1143 else:
1152 1144 s, self.buf = self.buf[:l], buffer(self.buf, l)
1153 1145 return s
1154 1146
1155 1147 def filechunkiter(f, size=65536, limit=None):
1156 1148 """Create a generator that produces the data in the file size
1157 1149 (default 65536) bytes at a time, up to optional limit (default is
1158 1150 to read all data). Chunks may be less than size bytes if the
1159 1151 chunk is the last chunk in the file, or the file is a socket or
1160 1152 some other type of file that sometimes reads less data than is
1161 1153 requested."""
1162 1154 assert size >= 0
1163 1155 assert limit is None or limit >= 0
1164 1156 while True:
1165 1157 if limit is None: nbytes = size
1166 1158 else: nbytes = min(limit, size)
1167 1159 s = nbytes and f.read(nbytes)
1168 1160 if not s: break
1169 1161 if limit: limit -= len(s)
1170 1162 yield s
1171 1163
1172 1164 def makedate():
1173 1165 lt = time.localtime()
1174 1166 if lt[8] == 1 and time.daylight:
1175 1167 tz = time.altzone
1176 1168 else:
1177 1169 tz = time.timezone
1178 1170 return time.mktime(lt), tz
1179 1171
1180 1172 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1181 1173 """represent a (unixtime, offset) tuple as a localized time.
1182 1174 unixtime is seconds since the epoch, and offset is the time zone's
1183 1175 number of seconds away from UTC. if timezone is false, do not
1184 1176 append time zone to string."""
1185 1177 t, tz = date or makedate()
1186 1178 if "%1" in format or "%2" in format:
1187 1179 sign = (tz > 0) and "-" or "+"
1188 1180 minutes = abs(tz) / 60
1189 1181 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1190 1182 format = format.replace("%2", "%02d" % (minutes % 60))
1191 1183 s = time.strftime(format, time.gmtime(float(t) - tz))
1192 1184 return s
1193 1185
1194 1186 def shortdate(date=None):
1195 1187 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1196 1188 return datestr(date, format='%Y-%m-%d')
1197 1189
1198 1190 def strdate(string, format, defaults=[]):
1199 1191 """parse a localized time string and return a (unixtime, offset) tuple.
1200 1192 if the string cannot be parsed, ValueError is raised."""
1201 1193 def timezone(string):
1202 1194 tz = string.split()[-1]
1203 1195 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1204 1196 sign = (tz[0] == "+") and 1 or -1
1205 1197 hours = int(tz[1:3])
1206 1198 minutes = int(tz[3:5])
1207 1199 return -sign * (hours * 60 + minutes) * 60
1208 1200 if tz == "GMT" or tz == "UTC":
1209 1201 return 0
1210 1202 return None
1211 1203
1212 1204 # NOTE: unixtime = localunixtime + offset
1213 1205 offset, date = timezone(string), string
1214 1206 if offset != None:
1215 1207 date = " ".join(string.split()[:-1])
1216 1208
1217 1209 # add missing elements from defaults
1218 1210 for part in defaults:
1219 1211 found = [True for p in part if ("%"+p) in format]
1220 1212 if not found:
1221 1213 date += "@" + defaults[part]
1222 1214 format += "@%" + part[0]
1223 1215
1224 1216 timetuple = time.strptime(date, format)
1225 1217 localunixtime = int(calendar.timegm(timetuple))
1226 1218 if offset is None:
1227 1219 # local timezone
1228 1220 unixtime = int(time.mktime(timetuple))
1229 1221 offset = unixtime - localunixtime
1230 1222 else:
1231 1223 unixtime = localunixtime + offset
1232 1224 return unixtime, offset
1233 1225
1234 1226 def parsedate(date, formats=None, defaults=None):
1235 1227 """parse a localized date/time string and return a (unixtime, offset) tuple.
1236 1228
1237 1229 The date may be a "unixtime offset" string or in one of the specified
1238 1230 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1239 1231 """
1240 1232 if not date:
1241 1233 return 0, 0
1242 1234 if isinstance(date, tuple) and len(date) == 2:
1243 1235 return date
1244 1236 if not formats:
1245 1237 formats = defaultdateformats
1246 1238 date = date.strip()
1247 1239 try:
1248 1240 when, offset = map(int, date.split(' '))
1249 1241 except ValueError:
1250 1242 # fill out defaults
1251 1243 if not defaults:
1252 1244 defaults = {}
1253 1245 now = makedate()
1254 1246 for part in "d mb yY HI M S".split():
1255 1247 if part not in defaults:
1256 1248 if part[0] in "HMS":
1257 1249 defaults[part] = "00"
1258 1250 else:
1259 1251 defaults[part] = datestr(now, "%" + part[0])
1260 1252
1261 1253 for format in formats:
1262 1254 try:
1263 1255 when, offset = strdate(date, format, defaults)
1264 1256 except (ValueError, OverflowError):
1265 1257 pass
1266 1258 else:
1267 1259 break
1268 1260 else:
1269 1261 raise Abort(_('invalid date: %r ') % date)
1270 1262 # validate explicit (probably user-specified) date and
1271 1263 # time zone offset. values must fit in signed 32 bits for
1272 1264 # current 32-bit linux runtimes. timezones go from UTC-12
1273 1265 # to UTC+14
1274 1266 if abs(when) > 0x7fffffff:
1275 1267 raise Abort(_('date exceeds 32 bits: %d') % when)
1276 1268 if offset < -50400 or offset > 43200:
1277 1269 raise Abort(_('impossible time zone offset: %d') % offset)
1278 1270 return when, offset
1279 1271
1280 1272 def matchdate(date):
1281 1273 """Return a function that matches a given date match specifier
1282 1274
1283 1275 Formats include:
1284 1276
1285 1277 '{date}' match a given date to the accuracy provided
1286 1278
1287 1279 '<{date}' on or before a given date
1288 1280
1289 1281 '>{date}' on or after a given date
1290 1282
1291 1283 """
1292 1284
1293 1285 def lower(date):
1294 1286 d = dict(mb="1", d="1")
1295 1287 return parsedate(date, extendeddateformats, d)[0]
1296 1288
1297 1289 def upper(date):
1298 1290 d = dict(mb="12", HI="23", M="59", S="59")
1299 1291 for days in "31 30 29".split():
1300 1292 try:
1301 1293 d["d"] = days
1302 1294 return parsedate(date, extendeddateformats, d)[0]
1303 1295 except:
1304 1296 pass
1305 1297 d["d"] = "28"
1306 1298 return parsedate(date, extendeddateformats, d)[0]
1307 1299
1308 1300 date = date.strip()
1309 1301 if date[0] == "<":
1310 1302 when = upper(date[1:])
1311 1303 return lambda x: x <= when
1312 1304 elif date[0] == ">":
1313 1305 when = lower(date[1:])
1314 1306 return lambda x: x >= when
1315 1307 elif date[0] == "-":
1316 1308 try:
1317 1309 days = int(date[1:])
1318 1310 except ValueError:
1319 1311 raise Abort(_("invalid day spec: %s") % date[1:])
1320 1312 when = makedate()[0] - days * 3600 * 24
1321 1313 return lambda x: x >= when
1322 1314 elif " to " in date:
1323 1315 a, b = date.split(" to ")
1324 1316 start, stop = lower(a), upper(b)
1325 1317 return lambda x: x >= start and x <= stop
1326 1318 else:
1327 1319 start, stop = lower(date), upper(date)
1328 1320 return lambda x: x >= start and x <= stop
1329 1321
1330 1322 def shortuser(user):
1331 1323 """Return a short representation of a user name or email address."""
1332 1324 f = user.find('@')
1333 1325 if f >= 0:
1334 1326 user = user[:f]
1335 1327 f = user.find('<')
1336 1328 if f >= 0:
1337 1329 user = user[f+1:]
1338 1330 f = user.find(' ')
1339 1331 if f >= 0:
1340 1332 user = user[:f]
1341 1333 f = user.find('.')
1342 1334 if f >= 0:
1343 1335 user = user[:f]
1344 1336 return user
1345 1337
1346 1338 def email(author):
1347 1339 '''get email of author.'''
1348 1340 r = author.find('>')
1349 1341 if r == -1: r = None
1350 1342 return author[author.find('<')+1:r]
1351 1343
1352 1344 def ellipsis(text, maxlength=400):
1353 1345 """Trim string to at most maxlength (default: 400) characters."""
1354 1346 if len(text) <= maxlength:
1355 1347 return text
1356 1348 else:
1357 1349 return "%s..." % (text[:maxlength-3])
1358 1350
1359 1351 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1360 1352 '''yield every hg repository under path, recursively.'''
1361 1353 def errhandler(err):
1362 1354 if err.filename == path:
1363 1355 raise err
1364 1356 if followsym and hasattr(os.path, 'samestat'):
1365 1357 def _add_dir_if_not_there(dirlst, dirname):
1366 1358 match = False
1367 1359 samestat = os.path.samestat
1368 1360 dirstat = os.stat(dirname)
1369 1361 for lstdirstat in dirlst:
1370 1362 if samestat(dirstat, lstdirstat):
1371 1363 match = True
1372 1364 break
1373 1365 if not match:
1374 1366 dirlst.append(dirstat)
1375 1367 return not match
1376 1368 else:
1377 1369 followsym = False
1378 1370
1379 1371 if (seen_dirs is None) and followsym:
1380 1372 seen_dirs = []
1381 1373 _add_dir_if_not_there(seen_dirs, path)
1382 1374 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1383 1375 if '.hg' in dirs:
1384 1376 yield root # found a repository
1385 1377 qroot = os.path.join(root, '.hg', 'patches')
1386 1378 if os.path.isdir(os.path.join(qroot, '.hg')):
1387 1379 yield qroot # we have a patch queue repo here
1388 1380 if recurse:
1389 1381 # avoid recursing inside the .hg directory
1390 1382 dirs.remove('.hg')
1391 1383 else:
1392 1384 dirs[:] = [] # don't descend further
1393 1385 elif followsym:
1394 1386 newdirs = []
1395 1387 for d in dirs:
1396 1388 fname = os.path.join(root, d)
1397 1389 if _add_dir_if_not_there(seen_dirs, fname):
1398 1390 if os.path.islink(fname):
1399 1391 for hgname in walkrepos(fname, True, seen_dirs):
1400 1392 yield hgname
1401 1393 else:
1402 1394 newdirs.append(d)
1403 1395 dirs[:] = newdirs
1404 1396
1405 1397 _rcpath = None
1406 1398
1407 1399 def os_rcpath():
1408 1400 '''return default os-specific hgrc search path'''
1409 1401 path = system_rcpath()
1410 1402 path.extend(user_rcpath())
1411 1403 path = [os.path.normpath(f) for f in path]
1412 1404 return path
1413 1405
1414 1406 def rcpath():
1415 1407 '''return hgrc search path. if env var HGRCPATH is set, use it.
1416 1408 for each item in path, if directory, use files ending in .rc,
1417 1409 else use item.
1418 1410 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1419 1411 if no HGRCPATH, use default os-specific path.'''
1420 1412 global _rcpath
1421 1413 if _rcpath is None:
1422 1414 if 'HGRCPATH' in os.environ:
1423 1415 _rcpath = []
1424 1416 for p in os.environ['HGRCPATH'].split(os.pathsep):
1425 1417 if not p: continue
1426 1418 if os.path.isdir(p):
1427 1419 for f, kind in osutil.listdir(p):
1428 1420 if f.endswith('.rc'):
1429 1421 _rcpath.append(os.path.join(p, f))
1430 1422 else:
1431 1423 _rcpath.append(p)
1432 1424 else:
1433 1425 _rcpath = os_rcpath()
1434 1426 return _rcpath
1435 1427
1436 1428 def bytecount(nbytes):
1437 1429 '''return byte count formatted as readable string, with units'''
1438 1430
1439 1431 units = (
1440 1432 (100, 1<<30, _('%.0f GB')),
1441 1433 (10, 1<<30, _('%.1f GB')),
1442 1434 (1, 1<<30, _('%.2f GB')),
1443 1435 (100, 1<<20, _('%.0f MB')),
1444 1436 (10, 1<<20, _('%.1f MB')),
1445 1437 (1, 1<<20, _('%.2f MB')),
1446 1438 (100, 1<<10, _('%.0f KB')),
1447 1439 (10, 1<<10, _('%.1f KB')),
1448 1440 (1, 1<<10, _('%.2f KB')),
1449 1441 (1, 1, _('%.0f bytes')),
1450 1442 )
1451 1443
1452 1444 for multiplier, divisor, format in units:
1453 1445 if nbytes >= divisor * multiplier:
1454 1446 return format % (nbytes / float(divisor))
1455 1447 return units[-1][2] % nbytes
1456 1448
1457 1449 def drop_scheme(scheme, path):
1458 1450 sc = scheme + ':'
1459 1451 if path.startswith(sc):
1460 1452 path = path[len(sc):]
1461 1453 if path.startswith('//'):
1462 1454 path = path[2:]
1463 1455 return path
1464 1456
1465 1457 def uirepr(s):
1466 1458 # Avoid double backslash in Windows path repr()
1467 1459 return repr(s).replace('\\\\', '\\')
1468 1460
1469 1461 def termwidth():
1470 1462 if 'COLUMNS' in os.environ:
1471 1463 try:
1472 1464 return int(os.environ['COLUMNS'])
1473 1465 except ValueError:
1474 1466 pass
1475 1467 try:
1476 1468 import termios, array, fcntl
1477 1469 for dev in (sys.stdout, sys.stdin):
1478 1470 try:
1479 1471 fd = dev.fileno()
1480 1472 if not os.isatty(fd):
1481 1473 continue
1482 1474 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1483 1475 return array.array('h', arri)[1]
1484 1476 except ValueError:
1485 1477 pass
1486 1478 except ImportError:
1487 1479 pass
1488 1480 return 80
1489 1481
1490 1482 def iterlines(iterator):
1491 1483 for chunk in iterator:
1492 1484 for line in chunk.splitlines():
1493 1485 yield line
@@ -1,708 +1,704
1 1 #!/usr/bin/env python
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2, incorporated herein by reference.
9 9
10 10 import difflib
11 11 import errno
12 12 import optparse
13 13 import os
14 try:
15 import subprocess
16 subprocess.Popen # trigger ImportError early
17 closefds = os.name == 'posix'
18 def Popen4(cmd, bufsize=-1):
19 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
20 close_fds=closefds,
21 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
22 stderr=subprocess.STDOUT)
23 p.fromchild = p.stdout
24 p.tochild = p.stdin
25 p.childerr = p.stderr
26 return p
27 except ImportError:
28 subprocess = None
29 from popen2 import Popen4
14 import subprocess
30 15 import shutil
31 16 import signal
32 17 import sys
33 18 import tempfile
34 19 import time
35 20
21 closefds = os.name == 'posix'
22 def Popen4(cmd, bufsize=-1):
23 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
24 close_fds=closefds,
25 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
26 stderr=subprocess.STDOUT)
27 p.fromchild = p.stdout
28 p.tochild = p.stdin
29 p.childerr = p.stderr
30 return p
31
36 32 # reserved exit code to skip test (used by hghave)
37 33 SKIPPED_STATUS = 80
38 34 SKIPPED_PREFIX = 'skipped: '
39 35 FAILED_PREFIX = 'hghave check failed: '
40 36 PYTHON = sys.executable
41 37 hgpkg = None
42 38
43 39 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
44 40
45 41 defaults = {
46 42 'jobs': ('HGTEST_JOBS', 1),
47 43 'timeout': ('HGTEST_TIMEOUT', 180),
48 44 'port': ('HGTEST_PORT', 20059),
49 45 }
50 46
51 47 def parseargs():
52 48 parser = optparse.OptionParser("%prog [options] [tests]")
53 49 parser.add_option("-C", "--annotate", action="store_true",
54 50 help="output files annotated with coverage")
55 51 parser.add_option("--child", type="int",
56 52 help="run as child process, summary to given fd")
57 53 parser.add_option("-c", "--cover", action="store_true",
58 54 help="print a test coverage report")
59 55 parser.add_option("-f", "--first", action="store_true",
60 56 help="exit on the first test failure")
61 57 parser.add_option("-i", "--interactive", action="store_true",
62 58 help="prompt to accept changed output")
63 59 parser.add_option("-j", "--jobs", type="int",
64 60 help="number of jobs to run in parallel"
65 61 " (default: $%s or %d)" % defaults['jobs'])
66 62 parser.add_option("--keep-tmpdir", action="store_true",
67 63 help="keep temporary directory after running tests"
68 64 " (best used with --tmpdir)")
69 65 parser.add_option("-R", "--restart", action="store_true",
70 66 help="restart at last error")
71 67 parser.add_option("-p", "--port", type="int",
72 68 help="port on which servers should listen"
73 69 " (default: $%s or %d)" % defaults['port'])
74 70 parser.add_option("-r", "--retest", action="store_true",
75 71 help="retest failed tests")
76 72 parser.add_option("-s", "--cover_stdlib", action="store_true",
77 73 help="print a test coverage report inc. standard libraries")
78 74 parser.add_option("-t", "--timeout", type="int",
79 75 help="kill errant tests after TIMEOUT seconds"
80 76 " (default: $%s or %d)" % defaults['timeout'])
81 77 parser.add_option("--tmpdir", type="string",
82 78 help="run tests in the given temporary directory")
83 79 parser.add_option("-v", "--verbose", action="store_true",
84 80 help="output verbose messages")
85 81 parser.add_option("-n", "--nodiff", action="store_true",
86 82 help="skip showing test changes")
87 83 parser.add_option("--with-hg", type="string",
88 84 help="test existing install at given location")
89 85 parser.add_option("--pure", action="store_true",
90 86 help="use pure Python code instead of C extensions")
91 87
92 88 for option, default in defaults.items():
93 89 defaults[option] = int(os.environ.get(*default))
94 90 parser.set_defaults(**defaults)
95 91 (options, args) = parser.parse_args()
96 92
97 93 global vlog
98 94 options.anycoverage = (options.cover or
99 95 options.cover_stdlib or
100 96 options.annotate)
101 97
102 98 if options.verbose:
103 99 def vlog(*msg):
104 100 for m in msg:
105 101 print m,
106 102 print
107 103 else:
108 104 vlog = lambda *msg: None
109 105
110 106 if options.jobs < 1:
111 107 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
112 108 sys.exit(1)
113 109 if options.interactive and options.jobs > 1:
114 110 print '(--interactive overrides --jobs)'
115 111 options.jobs = 1
116 112
117 113 return (options, args)
118 114
119 115 def rename(src, dst):
120 116 """Like os.rename(), trade atomicity and opened files friendliness
121 117 for existing destination support.
122 118 """
123 119 shutil.copy(src, dst)
124 120 os.remove(src)
125 121
126 122 def splitnewlines(text):
127 123 '''like str.splitlines, but only split on newlines.
128 124 keep line endings.'''
129 125 i = 0
130 126 lines = []
131 127 while True:
132 128 n = text.find('\n', i)
133 129 if n == -1:
134 130 last = text[i:]
135 131 if last:
136 132 lines.append(last)
137 133 return lines
138 134 lines.append(text[i:n+1])
139 135 i = n + 1
140 136
141 137 def parsehghaveoutput(lines):
142 138 '''Parse hghave log lines.
143 139 Return tuple of lists (missing, failed):
144 140 * the missing/unknown features
145 141 * the features for which existence check failed'''
146 142 missing = []
147 143 failed = []
148 144 for line in lines:
149 145 if line.startswith(SKIPPED_PREFIX):
150 146 line = line.splitlines()[0]
151 147 missing.append(line[len(SKIPPED_PREFIX):])
152 148 elif line.startswith(FAILED_PREFIX):
153 149 line = line.splitlines()[0]
154 150 failed.append(line[len(FAILED_PREFIX):])
155 151
156 152 return missing, failed
157 153
158 154 def showdiff(expected, output):
159 155 for line in difflib.unified_diff(expected, output,
160 156 "Expected output", "Test output"):
161 157 sys.stdout.write(line)
162 158
163 159 def findprogram(program):
164 160 """Search PATH for a executable program"""
165 161 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
166 162 name = os.path.join(p, program)
167 163 if os.access(name, os.X_OK):
168 164 return name
169 165 return None
170 166
171 167 def checktools():
172 168 # Before we go any further, check for pre-requisite tools
173 169 # stuff from coreutils (cat, rm, etc) are not tested
174 170 for p in requiredtools:
175 171 if os.name == 'nt':
176 172 p += '.exe'
177 173 found = findprogram(p)
178 174 if found:
179 175 vlog("# Found prerequisite", p, "at", found)
180 176 else:
181 177 print "WARNING: Did not find prerequisite tool: "+p
182 178
183 179 def cleanup(options):
184 180 if not options.keep_tmpdir:
185 181 if options.verbose:
186 182 print "# Cleaning up HGTMP", HGTMP
187 183 shutil.rmtree(HGTMP, True)
188 184
189 185 def usecorrectpython():
190 186 # some tests run python interpreter. they must use same
191 187 # interpreter we use or bad things will happen.
192 188 exedir, exename = os.path.split(sys.executable)
193 189 if exename == 'python':
194 190 path = findprogram('python')
195 191 if os.path.dirname(path) == exedir:
196 192 return
197 193 vlog('# Making python executable in test path use correct Python')
198 194 mypython = os.path.join(BINDIR, 'python')
199 195 try:
200 196 os.symlink(sys.executable, mypython)
201 197 except AttributeError:
202 198 # windows fallback
203 199 shutil.copyfile(sys.executable, mypython)
204 200 shutil.copymode(sys.executable, mypython)
205 201
206 202 def installhg(options):
207 203 global PYTHON
208 204 vlog("# Performing temporary installation of HG")
209 205 installerrs = os.path.join("tests", "install.err")
210 206 pure = options.pure and "--pure" or ""
211 207
212 208 # Run installer in hg root
213 209 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
214 210 cmd = ('%s setup.py %s clean --all'
215 211 ' install --force --prefix="%s" --install-lib="%s"'
216 212 ' --install-scripts="%s" >%s 2>&1'
217 213 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
218 214 vlog("# Running", cmd)
219 215 if os.system(cmd) == 0:
220 216 if not options.verbose:
221 217 os.remove(installerrs)
222 218 else:
223 219 f = open(installerrs)
224 220 for line in f:
225 221 print line,
226 222 f.close()
227 223 sys.exit(1)
228 224 os.chdir(TESTDIR)
229 225
230 226 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
231 227
232 228 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
233 229 pythonpath = os.environ.get("PYTHONPATH")
234 230 if pythonpath:
235 231 pythonpath = pydir + os.pathsep + pythonpath
236 232 else:
237 233 pythonpath = pydir
238 234 os.environ["PYTHONPATH"] = pythonpath
239 235
240 236 usecorrectpython()
241 237 global hgpkg
242 238 hgpkg = _hgpath()
243 239
244 240 vlog("# Installing dummy diffstat")
245 241 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
246 242 f.write('#!' + sys.executable + '\n'
247 243 'import sys\n'
248 244 'files = 0\n'
249 245 'for line in sys.stdin:\n'
250 246 ' if line.startswith("diff "):\n'
251 247 ' files += 1\n'
252 248 'sys.stdout.write("files patched: %d\\n" % files)\n')
253 249 f.close()
254 250 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
255 251
256 252 if options.anycoverage:
257 253 vlog("# Installing coverage wrapper")
258 254 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
259 255 if os.path.exists(COVERAGE_FILE):
260 256 os.unlink(COVERAGE_FILE)
261 257 # Create a wrapper script to invoke hg via coverage.py
262 258 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
263 259 f = open(os.path.join(BINDIR, 'hg'), 'w')
264 260 f.write('#!' + sys.executable + '\n')
265 261 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
266 262 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
267 263 (os.path.join(TESTDIR, 'coverage.py'),
268 264 os.path.join(BINDIR, '_hg.py')))
269 265 f.close()
270 266 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
271 267 PYTHON = '"%s" "%s" -x' % (sys.executable,
272 268 os.path.join(TESTDIR,'coverage.py'))
273 269
274 270 def _hgpath():
275 271 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
276 272 hgpath = os.popen(cmd % PYTHON)
277 273 path = hgpath.read().strip()
278 274 hgpath.close()
279 275 return path
280 276
281 277 def outputcoverage(options):
282 278 vlog("# Producing coverage report")
283 279 omit = [BINDIR, TESTDIR, PYTHONDIR]
284 280 if not options.cover_stdlib:
285 281 # Exclude as system paths (ignoring empty strings seen on win)
286 282 omit += [x for x in sys.path if x != '']
287 283 omit = ','.join(omit)
288 284 os.chdir(PYTHONDIR)
289 285 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
290 286 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
291 287 vlog("# Running: "+cmd)
292 288 os.system(cmd)
293 289 if options.annotate:
294 290 adir = os.path.join(TESTDIR, 'annotated')
295 291 if not os.path.isdir(adir):
296 292 os.mkdir(adir)
297 293 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
298 294 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
299 295 adir, omit)
300 296 vlog("# Running: "+cmd)
301 297 os.system(cmd)
302 298
303 299 class Timeout(Exception):
304 300 pass
305 301
306 302 def alarmed(signum, frame):
307 303 raise Timeout
308 304
309 305 def run(cmd, options):
310 306 """Run command in a sub-process, capturing the output (stdout and stderr).
311 307 Return the exist code, and output."""
312 308 # TODO: Use subprocess.Popen if we're running on Python 2.4
313 309 if os.name == 'nt' or sys.platform.startswith('java'):
314 310 tochild, fromchild = os.popen4(cmd)
315 311 tochild.close()
316 312 output = fromchild.read()
317 313 ret = fromchild.close()
318 314 if ret == None:
319 315 ret = 0
320 316 else:
321 317 proc = Popen4(cmd)
322 318 try:
323 319 output = ''
324 320 proc.tochild.close()
325 321 output = proc.fromchild.read()
326 322 ret = proc.wait()
327 323 if os.WIFEXITED(ret):
328 324 ret = os.WEXITSTATUS(ret)
329 325 except Timeout:
330 326 vlog('# Process %d timed out - killing it' % proc.pid)
331 327 os.kill(proc.pid, signal.SIGTERM)
332 328 ret = proc.wait()
333 329 if ret == 0:
334 330 ret = signal.SIGTERM << 8
335 331 output += ("\n### Abort: timeout after %d seconds.\n"
336 332 % options.timeout)
337 333 return ret, splitnewlines(output)
338 334
339 335 def runone(options, test, skips, fails):
340 336 '''tristate output:
341 337 None -> skipped
342 338 True -> passed
343 339 False -> failed'''
344 340
345 341 def skip(msg):
346 342 if not options.verbose:
347 343 skips.append((test, msg))
348 344 else:
349 345 print "\nSkipping %s: %s" % (test, msg)
350 346 return None
351 347
352 348 def fail(msg):
353 349 fails.append((test, msg))
354 350 if not options.nodiff:
355 351 print "\nERROR: %s %s" % (test, msg)
356 352 return None
357 353
358 354 vlog("# Test", test)
359 355
360 356 # create a fresh hgrc
361 357 hgrc = file(HGRCPATH, 'w+')
362 358 hgrc.write('[ui]\n')
363 359 hgrc.write('slash = True\n')
364 360 hgrc.write('[defaults]\n')
365 361 hgrc.write('backout = -d "0 0"\n')
366 362 hgrc.write('commit = -d "0 0"\n')
367 363 hgrc.write('debugrawcommit = -d "0 0"\n')
368 364 hgrc.write('tag = -d "0 0"\n')
369 365 hgrc.close()
370 366
371 367 err = os.path.join(TESTDIR, test+".err")
372 368 ref = os.path.join(TESTDIR, test+".out")
373 369 testpath = os.path.join(TESTDIR, test)
374 370
375 371 if os.path.exists(err):
376 372 os.remove(err) # Remove any previous output files
377 373
378 374 # Make a tmp subdirectory to work in
379 375 tmpd = os.path.join(HGTMP, test)
380 376 os.mkdir(tmpd)
381 377 os.chdir(tmpd)
382 378
383 379 try:
384 380 tf = open(testpath)
385 381 firstline = tf.readline().rstrip()
386 382 tf.close()
387 383 except:
388 384 firstline = ''
389 385 lctest = test.lower()
390 386
391 387 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
392 388 cmd = '%s "%s"' % (PYTHON, testpath)
393 389 elif lctest.endswith('.bat'):
394 390 # do not run batch scripts on non-windows
395 391 if os.name != 'nt':
396 392 return skip("batch script")
397 393 # To reliably get the error code from batch files on WinXP,
398 394 # the "cmd /c call" prefix is needed. Grrr
399 395 cmd = 'cmd /c call "%s"' % testpath
400 396 else:
401 397 # do not run shell scripts on windows
402 398 if os.name == 'nt':
403 399 return skip("shell script")
404 400 # do not try to run non-executable programs
405 401 if not os.path.exists(testpath):
406 402 return fail("does not exist")
407 403 elif not os.access(testpath, os.X_OK):
408 404 return skip("not executable")
409 405 cmd = '"%s"' % testpath
410 406
411 407 if options.timeout > 0:
412 408 signal.alarm(options.timeout)
413 409
414 410 vlog("# Running", cmd)
415 411 ret, out = run(cmd, options)
416 412 vlog("# Ret was:", ret)
417 413
418 414 if options.timeout > 0:
419 415 signal.alarm(0)
420 416
421 417 mark = '.'
422 418
423 419 skipped = (ret == SKIPPED_STATUS)
424 420 # If reference output file exists, check test output against it
425 421 if os.path.exists(ref):
426 422 f = open(ref, "r")
427 423 refout = splitnewlines(f.read())
428 424 f.close()
429 425 else:
430 426 refout = []
431 427 if skipped:
432 428 mark = 's'
433 429 missing, failed = parsehghaveoutput(out)
434 430 if not missing:
435 431 missing = ['irrelevant']
436 432 if failed:
437 433 fail("hghave failed checking for %s" % failed[-1])
438 434 skipped = False
439 435 else:
440 436 skip(missing[-1])
441 437 elif out != refout:
442 438 mark = '!'
443 439 if ret:
444 440 fail("output changed and returned error code %d" % ret)
445 441 else:
446 442 fail("output changed")
447 443 if not options.nodiff:
448 444 showdiff(refout, out)
449 445 ret = 1
450 446 elif ret:
451 447 mark = '!'
452 448 fail("returned error code %d" % ret)
453 449
454 450 if not options.verbose:
455 451 sys.stdout.write(mark)
456 452 sys.stdout.flush()
457 453
458 454 if ret != 0 and not skipped:
459 455 # Save errors to a file for diagnosis
460 456 f = open(err, "wb")
461 457 for line in out:
462 458 f.write(line)
463 459 f.close()
464 460
465 461 # Kill off any leftover daemon processes
466 462 try:
467 463 fp = file(DAEMON_PIDS)
468 464 for line in fp:
469 465 try:
470 466 pid = int(line)
471 467 except ValueError:
472 468 continue
473 469 try:
474 470 os.kill(pid, 0)
475 471 vlog('# Killing daemon process %d' % pid)
476 472 os.kill(pid, signal.SIGTERM)
477 473 time.sleep(0.25)
478 474 os.kill(pid, 0)
479 475 vlog('# Daemon process %d is stuck - really killing it' % pid)
480 476 os.kill(pid, signal.SIGKILL)
481 477 except OSError, err:
482 478 if err.errno != errno.ESRCH:
483 479 raise
484 480 fp.close()
485 481 os.unlink(DAEMON_PIDS)
486 482 except IOError:
487 483 pass
488 484
489 485 os.chdir(TESTDIR)
490 486 if not options.keep_tmpdir:
491 487 shutil.rmtree(tmpd, True)
492 488 if skipped:
493 489 return None
494 490 return ret == 0
495 491
496 492 def runchildren(options, expecthg, tests):
497 493 if not options.with_hg:
498 494 installhg(options)
499 495 if hgpkg != expecthg:
500 496 print '# Testing unexpected mercurial: %s' % hgpkg
501 497
502 498 optcopy = dict(options.__dict__)
503 499 optcopy['jobs'] = 1
504 500 optcopy['with_hg'] = INST
505 501 opts = []
506 502 for opt, value in optcopy.iteritems():
507 503 name = '--' + opt.replace('_', '-')
508 504 if value is True:
509 505 opts.append(name)
510 506 elif value is not None:
511 507 opts.append(name + '=' + str(value))
512 508
513 509 tests.reverse()
514 510 jobs = [[] for j in xrange(options.jobs)]
515 511 while tests:
516 512 for job in jobs:
517 513 if not tests: break
518 514 job.append(tests.pop())
519 515 fps = {}
520 516 for j, job in enumerate(jobs):
521 517 if not job:
522 518 continue
523 519 rfd, wfd = os.pipe()
524 520 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
525 521 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
526 522 vlog(' '.join(cmdline))
527 523 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
528 524 os.close(wfd)
529 525 failures = 0
530 526 tested, skipped, failed = 0, 0, 0
531 527 skips = []
532 528 fails = []
533 529 while fps:
534 530 pid, status = os.wait()
535 531 fp = fps.pop(pid)
536 532 l = fp.read().splitlines()
537 533 test, skip, fail = map(int, l[:3])
538 534 split = -fail or len(l)
539 535 for s in l[3:split]:
540 536 skips.append(s.split(" ", 1))
541 537 for s in l[split:]:
542 538 fails.append(s.split(" ", 1))
543 539 tested += test
544 540 skipped += skip
545 541 failed += fail
546 542 vlog('pid %d exited, status %d' % (pid, status))
547 543 failures |= status
548 544 print
549 545 for s in skips:
550 546 print "Skipped %s: %s" % (s[0], s[1])
551 547 for s in fails:
552 548 print "Failed %s: %s" % (s[0], s[1])
553 549
554 550 if hgpkg != expecthg:
555 551 print '# Tested unexpected mercurial: %s' % hgpkg
556 552 print "# Ran %d tests, %d skipped, %d failed." % (
557 553 tested, skipped, failed)
558 554 sys.exit(failures != 0)
559 555
560 556 def runtests(options, expecthg, tests):
561 557 global DAEMON_PIDS, HGRCPATH
562 558 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
563 559 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
564 560
565 561 try:
566 562 if not options.with_hg:
567 563 installhg(options)
568 564
569 565 if hgpkg != expecthg:
570 566 print '# Testing unexpected mercurial: %s' % hgpkg
571 567
572 568 if options.timeout > 0:
573 569 try:
574 570 signal.signal(signal.SIGALRM, alarmed)
575 571 vlog('# Running tests with %d-second timeout' %
576 572 options.timeout)
577 573 except AttributeError:
578 574 print 'WARNING: cannot run tests with timeouts'
579 575 options.timeout = 0
580 576
581 577 tested = 0
582 578 failed = 0
583 579 skipped = 0
584 580
585 581 if options.restart:
586 582 orig = list(tests)
587 583 while tests:
588 584 if os.path.exists(tests[0] + ".err"):
589 585 break
590 586 tests.pop(0)
591 587 if not tests:
592 588 print "running all tests"
593 589 tests = orig
594 590
595 591 skips = []
596 592 fails = []
597 593 for test in tests:
598 594 if options.retest and not os.path.exists(test + ".err"):
599 595 skipped += 1
600 596 continue
601 597 ret = runone(options, test, skips, fails)
602 598 if ret is None:
603 599 skipped += 1
604 600 elif not ret:
605 601 if options.interactive:
606 602 print "Accept this change? [n] ",
607 603 answer = sys.stdin.readline().strip()
608 604 if answer.lower() in "y yes".split():
609 605 rename(test + ".err", test + ".out")
610 606 tested += 1
611 607 fails.pop()
612 608 continue
613 609 failed += 1
614 610 if options.first:
615 611 break
616 612 tested += 1
617 613
618 614 if options.child:
619 615 fp = os.fdopen(options.child, 'w')
620 616 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
621 617 for s in skips:
622 618 fp.write("%s %s\n" % s)
623 619 for s in fails:
624 620 fp.write("%s %s\n" % s)
625 621 fp.close()
626 622 else:
627 623 print
628 624 for s in skips:
629 625 print "Skipped %s: %s" % s
630 626 for s in fails:
631 627 print "Failed %s: %s" % s
632 628 if hgpkg != expecthg:
633 629 print '# Tested unexpected mercurial: %s' % hgpkg
634 630 print "# Ran %d tests, %d skipped, %d failed." % (
635 631 tested, skipped, failed)
636 632
637 633 if options.anycoverage:
638 634 outputcoverage(options)
639 635 except KeyboardInterrupt:
640 636 failed = True
641 637 print "\ninterrupted!"
642 638
643 639 if failed:
644 640 sys.exit(1)
645 641
646 642 def main():
647 643 (options, args) = parseargs()
648 644 if not options.child:
649 645 os.umask(022)
650 646
651 647 checktools()
652 648
653 649 # Reset some environment variables to well-known values so that
654 650 # the tests produce repeatable output.
655 651 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
656 652 os.environ['TZ'] = 'GMT'
657 653 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
658 654 os.environ['CDPATH'] = ''
659 655
660 656 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
661 657 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
662 658 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
663 659 options.tmpdir))
664 660 DAEMON_PIDS = None
665 661 HGRCPATH = None
666 662
667 663 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
668 664 os.environ["HGMERGE"] = "internal:merge"
669 665 os.environ["HGUSER"] = "test"
670 666 os.environ["HGENCODING"] = "ascii"
671 667 os.environ["HGENCODINGMODE"] = "strict"
672 668 os.environ["HGPORT"] = str(options.port)
673 669 os.environ["HGPORT1"] = str(options.port + 1)
674 670 os.environ["HGPORT2"] = str(options.port + 2)
675 671
676 672 if options.with_hg:
677 673 INST = options.with_hg
678 674 else:
679 675 INST = os.path.join(HGTMP, "install")
680 676 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
681 677 PYTHONDIR = os.path.join(INST, "lib", "python")
682 678 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
683 679
684 680 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
685 681
686 682 if len(args) == 0:
687 683 args = os.listdir(".")
688 684 args.sort()
689 685
690 686 tests = []
691 687 for test in args:
692 688 if (test.startswith("test-") and '~' not in test and
693 689 ('.' not in test or test.endswith('.py') or
694 690 test.endswith('.bat'))):
695 691 tests.append(test)
696 692
697 693 vlog("# Using TESTDIR", TESTDIR)
698 694 vlog("# Using HGTMP", HGTMP)
699 695
700 696 try:
701 697 if len(tests) > 1 and options.jobs > 1:
702 698 runchildren(options, expecthg, tests)
703 699 else:
704 700 runtests(options, expecthg, tests)
705 701 finally:
706 702 cleanup(options)
707 703
708 704 main()
General Comments 0
You need to be logged in to leave comments. Login now