519 def escapef(m): |
519 def escapef(m): |
520 return escapemap[m.group(0)] |
520 return escapemap[m.group(0)] |
521 def stringescape(s): |
521 def stringescape(s): |
522 return escapesub(escapef, s) |
522 return escapesub(escapef, s) |
523 |
523 |
524 def transformtst(lines): |
524 def rematch(el, l): |
525 inblock = False |
525 try: |
526 for l in lines: |
526 # ensure that the regex matches to the end of the string |
527 if inblock: |
527 return re.match(el + r'\Z', l) |
528 if l.startswith(' $ ') or not l.startswith(' '): |
528 except re.error: |
529 inblock = False |
529 # el is an invalid regex |
530 yield ' > EOF\n' |
530 return False |
531 yield l |
531 |
532 else: |
532 def globmatch(el, l): |
533 yield ' > ' + l[2:] |
533 # The only supported special characters are * and ?. Escaping is |
|
534 # supported. |
|
535 i, n = 0, len(el) |
|
536 res = '' |
|
537 while i < n: |
|
538 c = el[i] |
|
539 i += 1 |
|
540 if c == '\\' and el[i] in '*?\\': |
|
541 res += el[i - 1:i + 1] |
|
542 i += 1 |
|
543 elif c == '*': |
|
544 res += '.*' |
|
545 elif c == '?': |
|
546 res += '.' |
534 else: |
547 else: |
535 if l.startswith(' >>> '): |
548 res += re.escape(c) |
536 inblock = True |
549 return rematch(res, l) |
537 yield ' $ %s -m heredoctest <<EOF\n' % PYTHON |
550 |
538 yield ' > ' + l[2:] |
551 def linematch(el, l): |
539 else: |
552 if el == l: # perfect match (fast) |
540 yield l |
553 return True |
541 if inblock: |
554 if (el and |
542 yield ' > EOF\n' |
555 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or |
|
556 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l) or |
|
557 el.endswith(" (esc)\n") and el.decode('string-escape') == l)): |
|
558 return True |
|
559 return False |
543 |
560 |
544 def tsttest(test, wd, options, replacements): |
561 def tsttest(test, wd, options, replacements): |
545 t = open(test) |
562 # We generate a shell script which outputs unique markers to line |
546 out = [] |
563 # up script results with our source. These markers include input |
|
564 # line number and the last return code |
|
565 salt = "SALT" + str(time.time()) |
|
566 def addsalt(line): |
|
567 script.append('echo %s %s $?\n' % (salt, line)) |
|
568 |
|
569 # After we run the shell script, we re-unify the script output |
|
570 # with non-active parts of the source, with synchronization by our |
|
571 # SALT line number markers. The after table contains the |
|
572 # non-active components, ordered by line number |
|
573 after = {} |
|
574 pos = prepos = -1 |
|
575 |
|
576 # Expected shellscript output |
|
577 expected = {} |
|
578 |
|
579 # We keep track of whether or not we're in a Python block so we |
|
580 # can generate the surrounding doctest magic |
|
581 inpython = False |
|
582 |
|
583 f = open(test) |
|
584 t = f.readlines() |
|
585 f.close() |
|
586 |
547 script = [] |
587 script = [] |
548 salt = "SALT" + str(time.time()) |
588 for n, l in enumerate(t): |
549 |
|
550 pos = prepos = -1 |
|
551 after = {} |
|
552 expected = {} |
|
553 for n, l in enumerate(transformtst(t)): |
|
554 if not l.endswith('\n'): |
589 if not l.endswith('\n'): |
555 l += '\n' |
590 l += '\n' |
556 if l.startswith(' $ '): # commands |
591 if l.startswith(' >>> '): # python inlines |
|
592 if not inpython: |
|
593 # we've just entered a Python block, add the header |
|
594 inpython = True |
|
595 addsalt(n) |
|
596 script.append('%s -m heredoctest <<EOF\n' % PYTHON) |
|
597 prepos = pos |
|
598 pos = n |
|
599 after.setdefault(prepos, []).append(l) |
|
600 script.append(l[2:]) |
|
601 elif l.startswith(' $ '): # commands |
|
602 if inpython: |
|
603 script.append("EOF\n") |
|
604 inpython = False |
557 after.setdefault(pos, []).append(l) |
605 after.setdefault(pos, []).append(l) |
558 prepos = pos |
606 prepos = pos |
559 pos = n |
607 pos = n |
560 script.append('echo %s %s $?\n' % (salt, n)) |
608 addsalt(n) |
561 script.append(l[4:]) |
609 script.append(l[4:]) |
562 elif l.startswith(' > '): # continuations |
610 elif l.startswith(' > '): # continuations |
563 after.setdefault(prepos, []).append(l) |
611 after.setdefault(prepos, []).append(l) |
564 script.append(l[4:]) |
612 script.append(l[4:]) |
565 elif l.startswith(' '): # results |
613 elif l.startswith(' '): # results |
566 # queue up a list of expected results |
614 if inpython: |
567 expected.setdefault(pos, []).append(l[2:]) |
615 script.append(l[2:]) |
|
616 after.setdefault(prepos, []).append(l) |
|
617 else: |
|
618 # queue up a list of expected results |
|
619 expected.setdefault(pos, []).append(l[2:]) |
568 else: |
620 else: |
|
621 if inpython: |
|
622 script.append("EOF\n") |
|
623 inpython = False |
569 # non-command/result - queue up for merged output |
624 # non-command/result - queue up for merged output |
570 after.setdefault(pos, []).append(l) |
625 after.setdefault(pos, []).append(l) |
571 |
626 |
572 t.close() |
627 if inpython: |
573 |
628 script.append("EOF\n") |
574 script.append('echo %s %s $?\n' % (salt, n + 1)) |
629 addsalt(n + 1) |
575 |
630 |
|
631 # Write out the script and execute it |
576 fd, name = tempfile.mkstemp(suffix='hg-tst') |
632 fd, name = tempfile.mkstemp(suffix='hg-tst') |
577 |
|
578 try: |
633 try: |
579 for l in script: |
634 for l in script: |
580 os.write(fd, l) |
635 os.write(fd, l) |
581 os.close(fd) |
636 os.close(fd) |
582 |
637 |
588 if exitcode == SKIPPED_STATUS or output is None: |
643 if exitcode == SKIPPED_STATUS or output is None: |
589 return exitcode, output |
644 return exitcode, output |
590 finally: |
645 finally: |
591 os.remove(name) |
646 os.remove(name) |
592 |
647 |
593 def rematch(el, l): |
648 # Merge the script output back into a unified test |
594 try: |
|
595 # ensure that the regex matches to the end of the string |
|
596 return re.match(el + r'\Z', l) |
|
597 except re.error: |
|
598 # el is an invalid regex |
|
599 return False |
|
600 |
|
601 def globmatch(el, l): |
|
602 # The only supported special characters are * and ?. Escaping is |
|
603 # supported. |
|
604 i, n = 0, len(el) |
|
605 res = '' |
|
606 while i < n: |
|
607 c = el[i] |
|
608 i += 1 |
|
609 if c == '\\' and el[i] in '*?\\': |
|
610 res += el[i - 1:i + 1] |
|
611 i += 1 |
|
612 elif c == '*': |
|
613 res += '.*' |
|
614 elif c == '?': |
|
615 res += '.' |
|
616 else: |
|
617 res += re.escape(c) |
|
618 return rematch(res, l) |
|
619 |
649 |
620 pos = -1 |
650 pos = -1 |
621 postout = [] |
651 postout = [] |
622 ret = 0 |
652 ret = 0 |
623 for n, l in enumerate(output): |
653 for n, l in enumerate(output): |
625 if salt in l: |
655 if salt in l: |
626 lout, lcmd = l.split(salt, 1) |
656 lout, lcmd = l.split(salt, 1) |
627 |
657 |
628 if lout: |
658 if lout: |
629 if lcmd: |
659 if lcmd: |
|
660 # output block had no trailing newline, clean up |
630 lout += ' (no-eol)\n' |
661 lout += ' (no-eol)\n' |
631 |
662 |
|
663 # find the expected output at the current position |
632 el = None |
664 el = None |
633 if pos in expected and expected[pos]: |
665 if pos in expected and expected[pos]: |
634 el = expected[pos].pop(0) |
666 el = expected[pos].pop(0) |
635 |
667 |
636 if el == lout: # perfect match (fast) |
668 if linematch(el, lout): |
637 postout.append(" " + lout) |
669 postout.append(" " + el) |
638 elif (el and |
|
639 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', lout) or |
|
640 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', lout) |
|
641 or el.endswith(" (esc)\n") and |
|
642 el.decode('string-escape') == l)): |
|
643 postout.append(" " + el) # fallback regex/glob/esc match |
|
644 else: |
670 else: |
645 if needescape(lout): |
671 if needescape(lout): |
646 lout = stringescape(lout.rstrip('\n')) + " (esc)\n" |
672 lout = stringescape(lout.rstrip('\n')) + " (esc)\n" |
647 postout.append(" " + lout) # let diff deal with it |
673 postout.append(" " + lout) # let diff deal with it |
648 |
674 |