git @ Cat's Eye Technologies Falderal / a50cb4d
Unsketch. Tests mostly pass. Chris Pressey 5 years ago
2 changed file(s) with 119 addition(s) and 313 deletion(s). Raw diff Collapse all Expand all
5454 for filename in args:
5555 documents.append(Document.load(filename))
5656 for document in documents:
57 tests += document.parse_blocks_to_tests(functionalities)
57 tests += document.parse_lines_to_tests(functionalities)
5858
5959 if not options.cavalier:
6060 if not documents:
171171 >>> b = Block()
172172 >>> b.append(u'-> This is a pragma.')
173173 >>> b.append(u'-> which extends over two lines')
174 >>> print b.split()
175 [Pragma(line_num=1)]
176
174 >>> print b.classify(ParseState())
175 Pragma(line_num=1)
176
177 >>> f = Functionality('foo')
177178 >>> b = Block()
178179 >>> b.append(u'| Test body here.')
179180 >>> b.append(u'= Expected result here.')
180 >>> print b.split()
181 [TestBody(line_num=1), ExpectedResult(line_num=1)]
182
181 >>> print b.classify(ParseState(current_functionality=f))
182 Test(body_block=TestBody(line_num=1), input_block=None,
183 expectation=OutputOutcome(u'Expected result here.'),
184 functionality=Functionality('foo'), desc_block=None,
185 body=u'Test body here.', input=None)
186
183187 >>> b = Block()
184188 >>> b.append(u'| Test body here.')
185189 >>> b.append(u'? Expected error here.')
186 >>> print b.split()
187 [TestBody(line_num=1), ExpectedError(line_num=1)]
190 >>> print b.classify(ParseState(current_functionality=f))
191 Test(body_block=TestBody(line_num=1), input_block=None,
192 expectation=ErrorOutcome(u'Expected error here.'),
193 functionality=Functionality('foo'), desc_block=None,
194 body=u'Test body here.', input=None)
188195
189196 """
190197
269276
270277 return pairs
271278
272 def split(self):
273 """Return a list of Blocks of more specific classes."""
274
275 pattern = self.deconstruct()
276 pattern_prefixes = [p[0] for p in pattern]
277
278 if '' in pattern_prefixes:
279 # There is plain, non-prefixed text embedded somewhere in this Block.
280 # TODO: interpret this according to the new, not-yet-written rules.
281 return []
282 else:
283 if pattern_prefixes in [[u'= '], [u'? ']]:
284 raise FalderalSyntaxError(
285 ("line %d: " % self.line_num) +
286 "expectation must be preceded by test body or test input")
287
288 if pattern_prefixes in [[u'| ']]:
289 raise FalderalSyntaxError(
290 ("line %d: " % self.line_num) +
291 "test body must be followed by expectation or test input")
292
293 if pattern_prefixes in self.VALID_PATTERNS:
294 return [self.PREFIX_MAP[prefix](line_num=self.line_num, filename=self.filename, lines=lines) for (prefix, lines) in pattern]
295 raise FalderalSyntaxError(
296 ("line %d: " % self.line_num) +
297 "incorrectly formatted test block")
298
299279 def classify(self, state):
300280 """Return the Test or Pragma that this Block represents."""
301281
315295 if '' in pattern_prefixes:
316296 # There is plain, non-prefixed text embedded somewhere in this Block.
317297 # TODO: interpret this according to the new, not-yet-written rules.
318 return []
298 # For now, assume it is Just Indented Text And That Is OK.
299 return None
319300
320301 if pattern_prefixes in [[u'= '], [u'? ']]:
321302 raise FalderalSyntaxError(
358339 expectation=expectation,
359340 functionality=state.current_functionality,
360341 desc_block=state.last_desc_block)
361
342
343 state.last_test_body_block = body_block
344 #state.last_test_input_block = input_block
345
362346 return test
363347 else:
364348 raise FalderalSyntaxError(
416400
417401
418402 class ParseState(object):
419 def __init__(self):
403 def __init__(self, current_functionality=None):
420404 self.last_desc_block = None
421405 self.last_test_body_block = None
422406 self.last_test_input_block = None
423 self.current_functionality = None
407 self.current_functionality = current_functionality
424408 self.functionalities = None
425409
426410
451435 line = line.rstrip(u'\r\n')
452436 self.lines.append(line)
453437
454 def parse_lines_to_blocks(self):
455 r"""Parse the lines of the Document into Blocks.
438 def parse_lines_to_tests(self, functionalities):
439 r"""Parse the lines of the Document into Tests.
440
441 | >>> d = Document()
442 | >>> d.append(u'This is a test file.')
443 | >>> d.append(u' -> This is a pragma.')
444 | >>> d.append(u'')
445 | >>> d.append(u" | This is some test input.\n")
446 | >>> d.append(u" | It extends over two lines.")
447 | >>> d.append(u' ? Expected Error')
448 | >>> d.append(u'')
449 | >>> d.append(u' | Test with input')
450 | >>> d.append(u' + input-for-test')
451 | >>> d.append(u' = Expected result on output')
452 | >>> d.parse_lines_to_blocks()
453 | >>> [block.lines for block in d.blocks if isinstance(block, InterveningText)]
454 | [[u'This is a test file.']]
455 | >>> [b.__class__.__name__ for b in d.blocks]
456 | ['InterveningText', 'Pragma', 'TestBody', 'ExpectedError',
457 | 'TestBody', 'TestInput', 'ExpectedResult']
458 | >>> [b.line_num for b in d.blocks]
459 | [1, 2, 4, 4, 8, 8, 8]
460
461 >>> functionalities = {}
462 >>> d = Document()
463 >>> d.append(u"This is a text file.")
464 >>> d.append(u'It contains NO tests.')
465 >>> d.parse_lines_to_tests(functionalities)
466 []
456467
457468 >>> d = Document()
458469 >>> d.append(u'This is a test file.')
459 >>> d.append(u' -> This is a pragma.')
470 >>> d.append(u' -> Tests for functionality "Parse Thing"')
460471 >>> d.append(u'')
461 >>> d.append(u" | This is some test input.\n")
462 >>> d.append(u" | It extends over two lines.")
463 >>> d.append(u' ? Expected Error')
464 >>> d.append(u'')
465 >>> d.append(u' | Test with input')
466 >>> d.append(u' + input-for-test')
467 >>> d.append(u' = Expected result on output')
468 >>> d.parse_lines_to_blocks()
469 >>> [block.lines for block in d.blocks if isinstance(block, InterveningText)]
470 [[u'This is a test file.']]
471 >>> [b.__class__.__name__ for b in d.blocks]
472 ['InterveningText', 'Pragma', 'TestBody', 'ExpectedError',
473 'TestBody', 'TestInput', 'ExpectedResult']
474 >>> [b.line_num for b in d.blocks]
475 [1, 2, 4, 4, 8, 8, 8]
476
477 """
478 indent = None
479 blocks = []
480 line_num = 1
481 block = None
482
483 for line in self.lines:
484 # make sure we get a Block to start with
485 if indent is None:
486 if line.startswith(u' '):
487 indent = u''
488 else:
489 indent = u' '
490
491 if indent == u'':
492 if line.startswith(u' '):
493 indent = u' '
494 if block is not None:
495 blocks.append(block)
496 block = Block(
497 line_num=line_num,
498 filename=self.filename
499 )
500 elif indent == u' ':
501 if not line.startswith(u' '):
502 indent = u''
503 if block is not None:
504 blocks.append(block)
505 block = InterveningText(
506 line_num=line_num,
507 filename=self.filename
508 )
509
510 line = line[len(indent):]
511
512 block.append(line)
513 line_num += 1
514
515 if block is not None:
516 blocks.append(block)
517
518 # post-process blocks
519 new_blocks = []
520 for block in blocks:
521 if isinstance(block, InterveningText):
522 if block.is_empty():
523 continue
524 new_blocks.append(block)
525 else:
526 new_blocks.extend(block.split())
527 self.blocks = new_blocks
528
529 def parse_lines_to_tests(self, functionalities):
530 r"""Parse the lines of the Document into Tests.
531
532 >>> functionalities = {}
472 >>> d.append(u" | This is some test body.")
473 >>> d.append(u' = Expected result')
474 >>> d.parse_lines_to_tests(functionalities)
475 [Test(body_block=TestBody(line_num=4), input_block=None,
476 expectation=OutputOutcome(u'Expected result'),
477 functionality=Functionality(u'Parse Thing'),
478 desc_block=InterveningText(line_num=1),
479 body=u'This is some test body.', input=None)]
480
533481 >>> d = Document()
534482 >>> d.append(u'This is a test file.')
535483 >>> d.append(u' -> Tests for functionality "Parse Thing"')
566514 ErrorOutcome(u'Oops')]
567515 >>> [t.functionality.name for t in tests]
568516 [u'Parse Thing', u'Parse Thing', u'Parse Thing', u'Run Thing']
569 >>> sorted(funs.keys())
517 >>> sorted(functionalities.keys())
570518 [u'Parse Thing', u'Run Thing']
519
520 >>> d = Document()
521 >>> d.append(u" | This is some test body.")
522 >>> d.append(u' = Expected')
523 >>> d.parse_lines_to_tests({})
524 Traceback (most recent call last):
525 ...
526 FalderalSyntaxError: line 1: functionality under test not specified
527
528 >>> d = Document()
529 >>> d.append(u'This is a test file.')
530 >>> d.append(u' ? Expected Error')
531 >>> d.parse_lines_to_tests({})
532 Traceback (most recent call last):
533 ...
534 FalderalSyntaxError: line 2: expectation must be preceded by test body or test input
535
536 >>> d = Document()
537 >>> d.append(u' -> Hello, this is pragma')
538 >>> d.append(u' = Expected')
539 >>> d.parse_lines_to_tests({})
540 Traceback (most recent call last):
541 ...
542 FalderalSyntaxError: line 1: incorrectly formatted test block
543
544 >>> d = Document()
545 >>> d.append(u' | This is test')
546 >>> d.append(u'This is text')
547 >>> d.parse_lines_to_tests({})
548 Traceback (most recent call last):
549 ...
550 FalderalSyntaxError: line 1: test body must be followed by expectation or test input
551
552 >>> d = Document()
553 >>> d.append(u' -> Hello, this is pragma')
554 >>> d.append(u' + Input to where exactly?')
555 >>> d.parse_lines_to_tests({})
556 Traceback (most recent call last):
557 ...
558 FalderalSyntaxError: line 1: incorrectly formatted test block
559
560 >>> d = Document()
561 >>> funs = {}
562 >>> d.append(u' -> Functionality "Parse Stuff" is implemented by '
563 ... u'shell command "parse"')
564 >>> d.append(u'')
565 >>> d.append(u' -> Functionality "Parse Stuff" is')
566 >>> d.append(u' -> implemented by shell command "pxxxy"')
567 >>> tests = d.parse_lines_to_tests(funs)
568 >>> len(funs.keys())
569 1
570 >>> [i for i in funs["Parse Stuff"].implementations]
571 [ShellImplementation(u'parse'), ShellImplementation(u'pxxxy')]
571572
572573 """
573574 indent = None
630631 elif isinstance(test_or_pragma, Pragma):
631632 test_or_pragma.execute(state)
632633 else:
633 raise NotImplementedError('need Pragma or Test')
634
635 return tests
636
637 def parse_blocks_to_tests(self, functionalities):
638 r"""Assemble a list of Tests from the blocks in this Document.
639
640 >>> funs = {}
641 >>> d = Document()
642 >>> d.append(u"This is a text file.")
643 >>> d.append(u'It contains NO tests.')
644 >>> d.parse_blocks_to_tests(funs)
645 []
646
647 >>> d = Document()
648 >>> d.append(u'This is a test file.')
649 >>> d.append(u' -> Tests for functionality "Parse Thing"')
650 >>> d.append(u'')
651 >>> d.append(u" | This is some test body.")
652 >>> d.append(u" | It extends over two lines.")
653 >>> d.append(u' ? Expected Error')
654 >>> d.append(u'')
655 >>> d.append(u' | Test with input')
656 >>> d.append(u' + input-for-test')
657 >>> d.append(u' = Expected result on output')
658 >>> d.append(u'')
659 >>> d.append(u' + Other input-for-test')
660 >>> d.append(u' = Other Expected result on output')
661 >>> d.append(u'')
662 >>> d.append(u' -> Tests for functionality "Run Thing"')
663 >>> d.append(u'')
664 >>> d.append(u" | Thing")
665 >>> d.append(u' ? Oops')
666 >>> d.parse_lines_to_blocks()
667 >>> [b.__class__.__name__ for b in d.blocks]
668 ['InterveningText', 'Pragma', 'TestBody', 'ExpectedError',
669 'TestBody', 'TestInput', 'ExpectedResult',
670 'TestInput', 'ExpectedResult', 'Pragma', 'TestBody', 'ExpectedError']
671 >>> tests = d.parse_blocks_to_tests(funs)
672 >>> [t.body for t in tests]
673 [u'This is some test body.\nIt extends over two lines.',
674 u'Test with input', u'Test with input', u'Thing']
675 >>> [t.input_block for t in tests]
676 [None, TestInput(line_num=8), TestInput(line_num=12), None]
677 >>> tests[1].input_block.text()
678 u'input-for-test'
679 >>> tests[2].input_block.text()
680 u'Other input-for-test'
681 >>> [t.expectation for t in tests]
682 [ErrorOutcome(u'Expected Error'),
683 OutputOutcome(u'Expected result on output'),
684 OutputOutcome(u'Other Expected result on output'),
685 ErrorOutcome(u'Oops')]
686 >>> [t.functionality.name for t in tests]
687 [u'Parse Thing', u'Parse Thing', u'Parse Thing', u'Run Thing']
688 >>> sorted(funs.keys())
689 [u'Parse Thing', u'Run Thing']
690
691 >>> d = Document()
692 >>> d.append(u" | This is some test body.")
693 >>> d.append(u' = Expected')
694 >>> d.parse_blocks_to_tests({})
695 Traceback (most recent call last):
696 ...
697 FalderalSyntaxError: line 1: functionality under test not specified
698
699 >>> d = Document()
700 >>> d.append(u'This is a test file.')
701 >>> d.append(u' ? Expected Error')
702 >>> d.parse_blocks_to_tests({})
703 Traceback (most recent call last):
704 ...
705 FalderalSyntaxError: line 2: expectation must be preceded by test body or test input
706
707 >>> d = Document()
708 >>> d.append(u' -> Hello, this is pragma')
709 >>> d.append(u' = Expected')
710 >>> d.parse_blocks_to_tests({})
711 Traceback (most recent call last):
712 ...
713 FalderalSyntaxError: line 1: incorrectly formatted test block
714
715 >>> d = Document()
716 >>> d.append(u' | This is test')
717 >>> d.append(u'This is text')
718 >>> d.parse_blocks_to_tests({})
719 Traceback (most recent call last):
720 ...
721 FalderalSyntaxError: line 1: test body must be followed by expectation or test input
722
723 >>> d = Document()
724 >>> d.append(u' -> Hello, this is pragma')
725 >>> d.append(u' + Input to where exactly?')
726 >>> d.parse_blocks_to_tests({})
727 Traceback (most recent call last):
728 ...
729 FalderalSyntaxError: line 1: incorrectly formatted test block
730
731 >>> d = Document()
732 >>> funs = {}
733 >>> d.append(u' -> Functionality "Parse Stuff" is implemented by '
734 ... u'shell command "parse"')
735 >>> d.append(u'')
736 >>> d.append(u' -> Functionality "Parse Stuff" is')
737 >>> d.append(u' -> implemented by shell command "pxxxy"')
738 >>> tests = d.parse_blocks_to_tests(funs)
739 >>> len(funs.keys())
740 1
741 >>> [i for i in funs["Parse Stuff"].implementations]
742 [ShellImplementation(u'parse'), ShellImplementation(u'pxxxy')]
743
744 """
745 if self.blocks is None:
746 self.parse_lines_to_blocks()
747 tests = []
748 current_functionality = None
749 prev_block = None
750 last_desc_block = None
751 last_test_body_block = None
752 last_used_test_body_block = None
753 last_test_input_block = None
754 for block in self.blocks:
755 # First, handle ExpectedError/ExpectedOutcome blocks.
756 expectation_class = None
757 if isinstance(block, ExpectedError):
758 expectation_class = ErrorOutcome
759 if isinstance(block, ExpectedResult):
760 expectation_class = OutputOutcome
761 if expectation_class:
762 # Expectations must be preceded by TestBody or TestInput.
763 if not (isinstance(prev_block, TestBody) or isinstance(prev_block, TestInput)):
764 raise FalderalSyntaxError(
765 ("line %d: " % block.line_num) +
766 "expectation must be preceded by test body or test input")
767 if current_functionality is None:
768 raise FalderalSyntaxError(
769 ("line %d: " % block.line_num) +
770 "functionality under test not specified")
771 test = Test(body_block=last_test_body_block,
772 input_block=last_test_input_block,
773 expectation=expectation_class(block.text()),
774 functionality=current_functionality,
775 desc_block=last_desc_block)
776 tests.append(test)
777 last_used_test_body_block = last_test_body_block
778 last_test_body_block = None
779 last_test_input_block = None
780 elif isinstance(block, TestInput):
781 # First test input must be preceded by TestBody.
782 if not isinstance(prev_block, TestBody):
783 if last_used_test_body_block is None:
784 raise FalderalSyntaxError(
785 ("line %d: " % block.line_num) +
786 "test input must be preceded by test body")
787 else:
788 # Subsequent test input not preceded by body
789 # shares most recently defined body.
790 last_test_body_block = last_used_test_body_block
791 # If we see a TestInput block, record it.
792 last_test_input_block = block
793 elif isinstance(block, TestBody):
794 # If we see a TestBody block, record it.
795 last_test_body_block = block
796 else:
797 # All others must not follow TestBody, or TestInput, as those need to be
798 # followed by an expectation or test input
799 if isinstance(prev_block, TestBody):
800 raise FalderalSyntaxError(
801 ("line %d: " % block.line_num) +
802 "test body must be followed by expectation or test input")
803 if isinstance(prev_block, TestInput):
804 raise FalderalSyntaxError(
805 ("line %d: " % block.line_num) +
806 "test input must be followed by expectation")
807 if isinstance(block, Pragma):
808 pragma_text = block.text(seperator=' ')
809 match = re.match(r'^\s*Tests\s+for\s+functionality\s*\"(.*?)\"\s*$', pragma_text)
810 if match:
811 functionality_name = match.group(1)
812 current_functionality = functionalities.setdefault(
813 functionality_name,
814 Functionality(functionality_name)
815 )
816 match = re.match(r'^\s*Functionality\s*\"(.*?)\"\s*is\s+implemented\s+by\s+shell\s+command\s*\"(.*?)\"\s*$', pragma_text)
817 if match:
818 functionality_name = match.group(1)
819 command = match.group(2)
820 functionality = functionalities.setdefault(
821 functionality_name,
822 Functionality(functionality_name)
823 )
824 implementation = ShellImplementation(command)
825 functionality.add_implementation(implementation)
826 elif isinstance(block, InterveningText):
827 if not re.match(r'^\s*$', block.text(seperator=' ')):
828 last_desc_block = block
829 prev_block = block
634 raise NotImplementedError('need Pragma or Test, not ' + repr(test_or_pragma))
635
830636 return tests
831637
832638