├── .gitignore ├── .travis.yml ├── MANIFEST.in ├── README.rst ├── pyconcordion2 ├── __init__.py ├── base.py ├── commands.py ├── expression_parser.py ├── resources │ ├── embedded.css │ └── main.js └── tests │ ├── __init__.py │ ├── spec │ ├── Index.html │ ├── IndexTest.py │ ├── __init__.py │ ├── concordion │ │ ├── Concordion.html │ │ ├── ConcordionTest.py │ │ ├── Example.html │ │ ├── ExampleTest.py │ │ ├── __init__.py │ │ ├── command │ │ │ ├── Command.html │ │ │ ├── CommandTest.py │ │ │ ├── __init__.py │ │ │ ├── assertEquals │ │ │ │ ├── AssertEquals.html │ │ │ │ ├── AssertEqualsTest.py │ │ │ │ ├── Exceptions.html │ │ │ │ ├── ExceptionsTest.py │ │ │ │ ├── NestedActions.html │ │ │ │ ├── NestedActionsTest.py │ │ │ │ ├── NestedElements.html │ │ │ │ ├── NestedElementsTest.py │ │ │ │ ├── SupportedElements.html │ │ │ │ ├── SupportedElementsTest.py │ │ │ │ ├── __init__.py │ │ │ │ ├── nonString │ │ │ │ │ ├── NonString.html │ │ │ │ │ ├── NonStringTest.py │ │ │ │ │ ├── NullResult.html │ │ │ │ │ ├── NullResultTest.py │ │ │ │ │ ├── VoidResult.html │ │ │ │ │ ├── VoidResultTest.py │ │ │ │ │ └── __init__.py │ │ │ │ └── whitespace │ │ │ │ │ ├── LineContinuations.html │ │ │ │ │ ├── LineContinuationsTest.py │ │ │ │ │ ├── Normalization.html │ │ │ │ │ ├── NormalizationTest.py │ │ │ │ │ ├── Whitespace.html │ │ │ │ │ ├── WhitespaceTest.py │ │ │ │ │ └── __init__.py │ │ │ ├── assertFalse │ │ │ │ ├── AssertFalse.html │ │ │ │ ├── AssertFalseTest.py │ │ │ │ └── __init__.py │ │ │ ├── assertTrue │ │ │ │ ├── AssertTrue.html │ │ │ │ ├── AssertTrueTest.py │ │ │ │ └── __init__.py │ │ │ ├── echo │ │ │ │ ├── DisplayingNulls.html │ │ │ │ ├── DisplayingNullsTest.py │ │ │ │ ├── Echo.html │ │ │ │ ├── EchoTest.py │ │ │ │ ├── EscapingHtmlCharacters.html │ │ │ │ ├── EscapingHtmlCharactersTest.py │ │ │ │ └── __init__.py │ │ │ ├── execute │ │ │ │ ├── Execute.html │ │ │ │ ├── ExecuteTest.py │ │ │ │ ├── ExecutingTables.html │ │ │ │ ├── ExecutingTablesTest.py │ │ │ │ └── __init__.py │ │ │ ├── results │ │ │ │ ├── __init__.py │ │ │ │ ├── contentType │ │ │ │ │ ├── ContentType.html │ │ │ │ │ ├── ContentTypeTest.py │ │ │ │ │ └── __init__.py │ │ │ │ └── stylesheet │ │ │ │ │ ├── MissingHeadElement.html │ │ │ │ │ ├── MissingHeadElementTest.py │ │ │ │ │ ├── Stylesheet.html │ │ │ │ │ ├── StylesheetTest.py │ │ │ │ │ └── __init__.py │ │ │ ├── run │ │ │ │ ├── Run.html │ │ │ │ ├── RunTest.py │ │ │ │ └── __init__.py │ │ │ ├── set │ │ │ │ ├── Set.html │ │ │ │ ├── SetTest.py │ │ │ │ └── __init__.py │ │ │ └── verifyRows │ │ │ │ ├── TableBodySupport.html │ │ │ │ ├── TableBodySupportTest.py │ │ │ │ ├── VerifyRows.html │ │ │ │ ├── VerifyRowsTest.py │ │ │ │ ├── __init__.py │ │ │ │ └── results │ │ │ │ ├── MissingRows.html │ │ │ │ ├── MissingRowsTest.py │ │ │ │ ├── SurplusRows.html │ │ │ │ ├── SurplusRowsTest.py │ │ │ │ └── __init__.py │ │ ├── extension │ │ │ ├── CSSExtension.html │ │ │ ├── CSSExtensionTest.py │ │ │ ├── CustomCommand.html │ │ │ ├── CustomCommandTest.py │ │ │ ├── Extension.html │ │ │ ├── ExtensionConfiguration.html │ │ │ ├── ExtensionConfigurationTest.py │ │ │ ├── ExtensionTest.py │ │ │ ├── JavaScriptExtension.html │ │ │ ├── JavaScriptExtensionTest.py │ │ │ ├── ResourceExtension.html │ │ │ ├── ResourceExtensionTest.py │ │ │ ├── __init__.py │ │ │ └── listener │ │ │ │ ├── ExecuteTableListener.html │ │ │ │ ├── Listener.html │ │ │ │ ├── VerifyRowsListener.html │ │ │ │ └── __init__.py │ │ └── results │ │ │ ├── Results.html │ │ │ ├── ResultsTest.py │ │ │ ├── __init__.py │ │ │ ├── assertEquals │ │ │ ├── __init__.py │ │ │ ├── failure │ │ │ │ ├── Anchors.html │ │ │ │ ├── AnchorsTest.py │ │ │ │ ├── Empty.html │ │ │ │ ├── EmptyTest.py │ │ │ │ ├── Failure.html │ │ │ │ ├── FailureTest.py │ │ │ │ ├── NestedElements.html │ │ │ │ ├── NestedElementsTest.py │ │ │ │ └── __init__.py │ │ │ └── success │ │ │ │ ├── Empty.html │ │ │ │ ├── EmptyTest.py │ │ │ │ ├── HasAttributes.html │ │ │ │ ├── HasAttributesTest.py │ │ │ │ ├── HasClassAttribute.html │ │ │ │ ├── HasClassAttributeTest.py │ │ │ │ ├── Success.html │ │ │ │ ├── SuccessTest.py │ │ │ │ └── __init__.py │ │ │ └── assertTrue │ │ │ ├── __init__.py │ │ │ ├── failure │ │ │ ├── Failure.html │ │ │ ├── FailureTest.py │ │ │ └── __init__.py │ │ │ └── success │ │ │ ├── Success.html │ │ │ ├── SuccessTest.py │ │ │ └── __init__.py │ ├── examples │ │ ├── Demo.html │ │ ├── DemoTest.py │ │ ├── PartialMatches.html │ │ ├── PartialMatchesTest.py │ │ ├── Spike.html │ │ ├── SpikeTest.py │ │ └── __init__.py │ └── run_test.py │ ├── test_rig.py │ └── unit │ ├── __init__.py │ └── expression_parser_test.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | .idea/* 37 | *.iml 38 | 39 | # OS X crap 40 | .DS_Store 41 | 42 | # virtualenv 43 | env_concordion -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | env: 4 | global: 5 | - REPO="concordion/pyconcordion2" 6 | - CI_HOME=`pwd`/$REPO 7 | - PYTHONPATH="$CI_HOME/pyconcordion2:$PYTHONPATH" 8 | 9 | python: 10 | - "2.7" 11 | 12 | # command to install dependencies 13 | install: 14 | - pip install -r requirements.txt 15 | - pip install coveralls 16 | 17 | # command to run tests 18 | script: nosetests --with-coverage --cover-tests --cover-package=pyconcordion2 19 | 20 | after_success: 21 | coveralls 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | recursive-include pyconcordion2/resources * 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |Build Status| |Coverage Status| 2 | 3 | pyconcordion2 4 | ============= 5 | 6 | A python implementation of the Concordion Acceptance Testing framework. 7 | 8 | Installation 9 | ------------ 10 | 11 | ``$ pip install pyconcordion2`` 12 | 13 | Usage 14 | ----- 15 | 16 | Simply extend your python test cases from ConcordionTestCase 17 | 18 | ``from pyconcordion2 import ConcordionTestCase`` 19 | 20 | Execute as you would normal unittests. 21 | 22 | Key Differences 23 | --------------- 24 | 25 | This is not a 100% port of the original Concordion framework. If you 26 | found a differing behaviour please let me know. 27 | 28 | Differences: 29 | 30 | - Not possible to link to test data via CSV 31 | - Extensions are currently not supported. 32 | 33 | Development Setup 34 | ----------------- 35 | 36 | :: 37 | 38 | $ git clone git@github.com:concordion/pyconcordion2.git 39 | $ cd pyconcordion2 40 | $ virtualenv env_concordion 41 | $ source ./env_concordion/bin/activate 42 | $ pip install -r requirements.txt 43 | $ nosetests # to run tests 44 | 45 | Deploying on Pypi 46 | ----------------- 47 | 48 | :: 49 | $ python2 setup.py sdist bdist_wheel 50 | $ twine upload dist/* 51 | 52 | License 53 | ------- 54 | 55 | Copyright 2013 John Jiang 56 | 57 | Licensed under the Apache License, Version 2.0 (the "License"); you may 58 | not use this file except in compliance with the License. You may obtain 59 | a copy of the License at 60 | 61 | :: 62 | 63 | http://www.apache.org/licenses/LICENSE-2.0 64 | 65 | Unless required by applicable law or agreed to in writing, software 66 | distributed under the License is distributed on an "AS IS" BASIS, 67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 68 | See the License for the specific language governing permissions and 69 | limitations under the License. 70 | 71 | Attribution 72 | ----------- 73 | 74 | Thanks to the `Concordion team`_ for making the original framework. 75 | 76 | Thanks to JC Plessis for making `pyconcordion python port`_ 77 | 78 | .. _Concordion team: http://www.concordion.org/ 79 | .. _pyconcordion python port: https://code.google.com/p/pyconcordion/ 80 | 81 | .. |Build Status| image:: https://travis-ci.org/concordion/pyconcordion2.png 82 | :target: https://travis-ci.org/concordion/pyconcordion2 83 | .. |Coverage Status| image:: https://coveralls.io/repos/concordion/pyconcordion2/badge.png 84 | :target: https://coveralls.io/r/concordion/pyconcordion2 85 | -------------------------------------------------------------------------------- /pyconcordion2/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | 4 | __version__ = '0.15.1' 5 | -------------------------------------------------------------------------------- /pyconcordion2/base.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import inspect 3 | import logging 4 | import os 5 | import sys 6 | import tempfile 7 | import unittest 8 | 9 | from lxml import etree 10 | 11 | from commands import Commander 12 | 13 | logger = logging.getLogger(__file__) 14 | logger.level = logging.INFO 15 | 16 | TEMP_DIR = tempfile.gettempdir() 17 | 18 | 19 | class ConcordionTestCase(unittest.TestCase): 20 | extra_folder = "." 21 | 22 | stream_handler = logging.StreamHandler(sys.stdout) 23 | 24 | @classmethod 25 | def setUpClass(cls): 26 | logger.addHandler(cls.stream_handler) 27 | 28 | @classmethod 29 | def tearDownClass(cls): 30 | logger.removeHandler(cls.stream_handler) 31 | 32 | def runTest(self): 33 | # hack to prevent the base class to be run 34 | if self.__class__.__name__ == ConcordionTestCase.__name__: 35 | return True 36 | 37 | filename = self._find_spec() 38 | 39 | runner = Commander(self, filename) 40 | runner.process() 41 | 42 | runner.tree.xpath("//body")[0].insert(0, self.bread_crumb_tag()) 43 | self.__write(filename, runner.tree) 44 | self.assertTrue(runner.result.has_succeeded()) 45 | 46 | def _find_spec(self): 47 | """ 48 | We find the filename of the spec based on the name of the test. If the class ends in "test" we remove it. 49 | """ 50 | filename, ext = os.path.splitext(inspect.getfile(self.__class__)) 51 | if filename[-4:].lower() == "test": 52 | filename = filename[:-4] 53 | filename += ".html" 54 | with open(filename): # will raise exception if it doesn't exist 55 | return filename 56 | 57 | def bread_crumbs(self): 58 | head, tail = os.path.split(self.file_path()) 59 | crumbs = [] 60 | while True: 61 | head, tail = os.path.split(head) 62 | 63 | # we do this because capitalize() makes the first character uppercase and everything else lowercase 64 | base = os.path.join(head, tail, tail[0].upper() + tail[1:]) 65 | 66 | crumb = base + ".html" 67 | # we skip if we're looking at the current spec 68 | if crumb == self._find_spec(): 69 | continue 70 | if os.path.exists(crumb): 71 | crumbs.append(crumb) 72 | else: 73 | break 74 | return reversed(crumbs) 75 | 76 | def bread_crumb_tag(self): 77 | span_tag = etree.Element("span", {"class": "breadcrumbs"}) 78 | for crumb in self.bread_crumbs(): 79 | crumb_relpath = os.path.relpath(crumb, os.path.dirname(self.file_path())) 80 | a_tag = etree.Element("a", href=crumb_relpath) 81 | a_tag.text = os.path.splitext(os.path.basename(crumb_relpath))[0] 82 | a_tag.tail = " > " 83 | span_tag.append(a_tag) 84 | return span_tag 85 | 86 | def __write(self, filename, tree): 87 | output_dir = os.getenv('PYCONCORDION_OUTPUT', TEMP_DIR) 88 | output_dir = os.path.join(output_dir, self.extra_folder) 89 | if not os.path.isdir(output_dir): 90 | os.makedirs(output_dir) 91 | 92 | with open(os.path.join(output_dir, os.path.basename(filename)), "w") as f: 93 | logger.info("Saving to:\n%s" % f.name) 94 | f.write(etree.tostring(tree, pretty_print=True)) 95 | 96 | def file_path(self): 97 | return os.path.abspath(inspect.getfile(self.__class__)) 98 | -------------------------------------------------------------------------------- /pyconcordion2/commands.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from collections import OrderedDict 3 | from io import BytesIO 4 | from operator import attrgetter 5 | import imp 6 | import inspect 7 | import itertools 8 | import os 9 | import re 10 | import traceback 11 | import unittest 12 | 13 | from enum import Enum 14 | from lxml import etree 15 | from lxml import html 16 | 17 | import expression_parser 18 | 19 | truth_values = ['true', '1', 't', 'y', 'yes'] 20 | 21 | CHAR_SPACE = '\u00A0' 22 | 23 | CONCORDION_NAMESPACE = "http://www.concordion.org/2007/concordion" 24 | 25 | 26 | class Status(Enum): 27 | success = 1 28 | failure = 2 29 | ignored = 3 30 | 31 | 32 | class ResultEvent(object): 33 | def __init__(self, actual, expected): 34 | self.actual = actual 35 | self.expected = expected 36 | 37 | 38 | class Result(object): 39 | def __init__(self, tree): 40 | self.root_element = tree 41 | self.successes = tree.xpath("//*[contains(concat(' ', @class, ' '), ' success ')]") 42 | self.failures = tree.xpath("//*[contains(concat(' ', @class, ' '), ' failure ')]") 43 | self.missing = tree.xpath("//*[contains(concat(' ', @class, ' '), ' missing ')]") 44 | self.exceptions = tree.xpath("//*[contains(concat(' ', @class, ' '), ' exceptionMessage ')]") 45 | 46 | def last_failed_event(self): 47 | last_failed = self.failures[-1] 48 | actual = last_failed.xpath("//*[@class='actual']")[0].text 49 | expected = last_failed.xpath("//*[@class='expected']")[0].text 50 | return ResultEvent(actual, expected) 51 | 52 | @property 53 | def num_failure(self): 54 | return len(self.failures) 55 | 56 | @property 57 | def num_exception(self): 58 | return len(self.exceptions) 59 | 60 | @property 61 | def num_missing(self): 62 | return len(self.missing) 63 | 64 | @property 65 | def num_success(self): 66 | return len(self.successes) 67 | 68 | def has_failed(self): 69 | return bool(self.num_failure or self.num_exception or self.num_missing) 70 | 71 | def has_succeeded(self): 72 | return not self.has_failed() 73 | 74 | 75 | class Commander(object): 76 | def __init__(self, test, filename): 77 | self.test = test 78 | self.filename = filename 79 | self.tree = etree.parse(self.filename) 80 | self.args = {} 81 | self.commands = OrderedDict() 82 | 83 | def process(self): 84 | """ 85 | 1. Finds all concordion elements 86 | 2. Iterates over concordion attributes 87 | 3. Generates ordered dictionary of commands 88 | 4. Executes commands in order 89 | """ 90 | elements = self.__find_concordion_elements() 91 | 92 | for element in elements: 93 | for key, expression_str in element.attrib.items(): 94 | if CONCORDION_NAMESPACE not in key: # we ignore any attributes that are not concordion 95 | continue 96 | 97 | key = key.replace("{%s}" % CONCORDION_NAMESPACE, "") # we remove the namespace 98 | 99 | command_cls = command_mapper.get(key) 100 | command = command_cls(element, expression_str, self.test) 101 | 102 | if element.tag.lower() == "th": 103 | index = self.__find_th_index(element) 104 | command.index = index 105 | 106 | self.__add_to_commands_dict(command) 107 | 108 | self.__run_commands() 109 | self.__postprocess_tree() 110 | self.result = Result(self.tree) 111 | 112 | def __find_concordion_elements(self): 113 | """ 114 | Retrieves all etree elements with the concordion namespace 115 | """ 116 | return self.tree.xpath("""//*[namespace-uri()='{namespace}' or @*[namespace-uri()='{namespace}']]""".format( 117 | namespace=CONCORDION_NAMESPACE)) 118 | 119 | def __find_th_index(self, element): 120 | """ 121 | Returns the index of the given table header cell 122 | """ 123 | parent = element.getparent() 124 | for index, th_element in enumerate(parent.xpath("th")): 125 | if th_element == element: 126 | return index 127 | raise RuntimeError("Could not match command with table header") # should NEVER happen 128 | 129 | def __add_to_commands_dict(self, command): 130 | """ 131 | Given a command, we check to see if it's a child of another command. If it is we add it to the list of child 132 | commands. Otherwise we set it as a brand new command 133 | """ 134 | element = command.element 135 | while element.getparent() is not None: 136 | if element.getparent() in self.commands: 137 | self.commands[element.getparent()].children.append(command) 138 | return 139 | else: 140 | element = element.getparent() 141 | self.commands[command.element] = command 142 | 143 | def __run_commands(self): 144 | """ 145 | Runs each command in order 146 | """ 147 | for element, command in self.commands.items(): 148 | command.run() 149 | 150 | def __postprocess_tree(self): 151 | css_path = os.path.join(os.path.dirname(__file__), "resources", "embedded.css") 152 | css_contents = open(css_path, "rU").read() 153 | 154 | js_path = os.path.join(os.path.dirname(__file__), "resources", "main.js") 155 | js_contents = open(js_path, "rU").read() 156 | 157 | meta = etree.Element("meta") 158 | meta.attrib["http-equiv"] = "content-type" 159 | meta.attrib["content"] = "text/html; charset=UTF-8" 160 | meta.tail = "\n" 161 | 162 | head = self.tree.xpath("//head") 163 | if head: 164 | head[0].insert(0, meta) 165 | else: 166 | head = etree.Element("head") 167 | head.text = "\n" 168 | head.append(meta) 169 | 170 | for child in self.tree.getroot().getchildren(): 171 | if child.tag == "body": 172 | break 173 | head.append(child) 174 | head.tail = "\n" 175 | 176 | self.tree.getroot().insert(0, head) 177 | 178 | head = self.tree.xpath("//head")[0] 179 | style_tag = etree.Element("style", type="text/css") 180 | style_tag.text = css_contents 181 | head.insert(0, style_tag) 182 | 183 | js_tag = etree.Element("script") 184 | js_tag.text = js_contents 185 | jquery_tag = etree.Element("script", src="http://code.jquery.com/jquery-1.10.2.min.js") 186 | jquery_tag.text = " " 187 | 188 | self.tree.getroot().append(jquery_tag) 189 | self.tree.getroot().append(js_tag) 190 | 191 | 192 | class Command(object): 193 | def __init__(self, element, expression_str, context): 194 | self.element = element 195 | self.expression_str = expression_str.replace("#", "") 196 | self.context = context 197 | self.children = [] 198 | 199 | def _run(self): 200 | raise NotImplementedError 201 | 202 | def run(self): 203 | try: 204 | self.context.TEXT = get_element_content(self.element) 205 | self._run() 206 | return True 207 | except Exception as e: 208 | mark_exception(self.element, e) 209 | 210 | 211 | class RunCommand(Command): 212 | def _run(self): 213 | href = self.element.attrib["href"].replace(".html", "") 214 | f = inspect.getfile(self.context.__class__) 215 | file_path = os.path.join(os.path.dirname(os.path.abspath(f)), href) 216 | 217 | if os.path.exists(file_path + ".py"): 218 | src_file_path = file_path + ".py" 219 | elif os.path.exists(file_path + "Test.py"): 220 | src_file_path = file_path + "Test.py" 221 | else: 222 | raise RuntimeError("Cannot find Python Test file") 223 | 224 | modname, ext = os.path.splitext(os.path.basename(src_file_path)) 225 | test_class = imp.load_source(modname, src_file_path) 226 | 227 | test_class = getattr(test_class, modname)() 228 | test_class.extra_folder = os.path.dirname(os.path.join(self.context.extra_folder, href)) 229 | result = unittest.TextTestRunner().run(test_class) 230 | if result.failures or result.errors: 231 | mark_status(Status.failure, self.element) 232 | elif result.expectedFailures: 233 | mark_status(Status.ignored, self.element) 234 | else: 235 | mark_status(Status.success, self.element) 236 | 237 | 238 | class ExecuteCommand(Command): 239 | def _run(self): 240 | if self.element.tag.lower() == "table": 241 | for row in get_table_body_rows(self.element): 242 | for command in self.children: 243 | td_element = row.xpath("td")[command.index] 244 | command.element = td_element 245 | self._run_children() 246 | else: 247 | self._run_children() 248 | 249 | def _run_children(self): 250 | for command in self.children: 251 | if isinstance(command, SetCommand): 252 | command.run() 253 | expression_parser.execute_within_context(self.context, self.expression_str) 254 | for command in self.children: 255 | if not isinstance(command, SetCommand): 256 | command.run() 257 | 258 | 259 | class VerifyRowsCommand(Command): 260 | def _run(self): 261 | variable_name = expression_parser.parse(self.expression_str).variable_name 262 | results = expression_parser.execute_within_context(self.context, self.expression_str) 263 | for result, row in itertools.izip_longest(results, get_table_body_rows(self.element)): 264 | setattr(self.context, variable_name, result) 265 | 266 | if result is None: 267 | row.attrib["class"] = (row.attrib.get("class", "") + " missing").strip() 268 | continue 269 | 270 | if row is None: 271 | total_columns = max(self.children, key=attrgetter("index")).index + 1 # good enough but not perfect 272 | row = etree.Element("tr", **{"class": "surplus"}) 273 | for _ in xrange(total_columns): 274 | etree.SubElement(row, "td") 275 | if self.element.xpath("//tbody"): 276 | self.element.xpath("//tbody")[0].append(row) 277 | else: 278 | self.element.append(row) 279 | 280 | for command in self.children: 281 | element = row.xpath("td")[command.index] 282 | command.element = element 283 | command.run() 284 | 285 | 286 | def get_table_body_rows(table): 287 | if table.xpath("//tbody"): 288 | tr_s = table.xpath("//tbody/tr") 289 | else: 290 | tr_s = table.xpath("tr") 291 | return [tr for tr in tr_s if tr.xpath("td")] 292 | 293 | 294 | def normalize(text): 295 | text = unicode(text) 296 | text = text.replace(" _\n", "") # support for python style line breaks 297 | pattern = re.compile(r'\s+') # treat all whitespace as spaces 298 | return re.sub(pattern, ' ', text).strip() 299 | 300 | 301 | def get_element_content(element): 302 | tag_html = html.parse(BytesIO(etree.tostring(element))).getroot().getchildren()[0].getchildren()[0] 303 | return normalize(tag_html.text_content()) 304 | 305 | 306 | class SetCommand(Command): 307 | def _run(self): 308 | expression = expression_parser.parse(self.expression_str) 309 | if expression.function_name: # concordion:set="blah = function(#TEXT)" 310 | expression_parser.execute_within_context(self.context, self.expression_str) 311 | else: 312 | setattr(self.context, expression.variable_name, get_element_content(self.element)) 313 | 314 | 315 | class AssertEqualsCommand(Command): 316 | def _run(self): 317 | expression_return = expression_parser.execute_within_context(self.context, self.expression_str) 318 | if expression_return is None: 319 | expression_return = "(None)" 320 | 321 | result = normalize(expression_return) == get_element_content(self.element) 322 | if result: 323 | mark_status(Status.success, self.element) 324 | else: 325 | mark_status(Status.failure, self.element, expression_return) 326 | 327 | 328 | class AssertTrueCommand(Command): 329 | def _run(self): 330 | result = expression_parser.execute_within_context(self.context, self.expression_str) 331 | if result: 332 | mark_status(Status.success, self.element) 333 | else: 334 | mark_status(Status.failure, self.element, "== false") 335 | 336 | 337 | class AssertFalseCommand(Command): 338 | def _run(self): 339 | result = expression_parser.execute_within_context(self.context, self.expression_str) 340 | if not result: 341 | mark_status(Status.success, self.element) 342 | else: 343 | mark_status(Status.failure, self.element, "== true") 344 | 345 | 346 | class EchoCommand(Command): 347 | def _run(self): 348 | result = expression_parser.execute_within_context(self.context, self.expression_str) 349 | if result is not None: 350 | self.element.text = result 351 | else: 352 | em = etree.Element("em") 353 | em.text = "None" 354 | self.element.append(em) 355 | 356 | 357 | def mark_status(status, element, actual_value=None): 358 | if not get_element_content(element): # set non-breaking space if element is empty 359 | element.text = CHAR_SPACE 360 | 361 | element.attrib["class"] = (element.attrib.get("class", "") + " {}".format(status.name)).strip() 362 | if actual_value is not None: 363 | actual = etree.Element("ins", **{"class": "actual"}) 364 | actual.text = unicode(actual_value) or CHAR_SPACE # blank space if no value 365 | 366 | # we move child elements from element into our new del container 367 | expected = etree.Element("del", **{"class": "expected"}) 368 | for child in element.getchildren(): 369 | expected.append(child) 370 | expected.text = element.text 371 | element.text = None 372 | expected.tail = "\n" 373 | 374 | element.insert(0, expected) 375 | element.insert(1, actual) 376 | 377 | 378 | __exception_index = 1 379 | 380 | 381 | def mark_exception(target_element, e): 382 | global __exception_index 383 | exception_element = etree.Element("span", **{"class": "exceptionMessage"}) 384 | exception_element.text = unicode(e) 385 | 386 | input_element = etree.Element("input", 387 | **{"class": "stackTraceButton", "data-exception-index": unicode(__exception_index), 388 | "type": "button", "value": "Toggle Stack"}) 389 | 390 | stacktrace_div_element = etree.Element("div", **{"class": "stackTrace {}".format(__exception_index)}) 391 | p_tag = etree.Element("p") 392 | p_tag.text = "Traceback:" 393 | stacktrace_div_element.append(p_tag) 394 | tb = traceback.format_exc() 395 | for line in tb.splitlines(): 396 | trace_element = etree.Element("div", **{"class": "stackTraceEntry"}) 397 | trace_element.text = line 398 | stacktrace_div_element.append(trace_element) 399 | 400 | parent = target_element.getparent() 401 | # we insert the exception after the element in question 402 | for i, element in enumerate((exception_element, input_element, stacktrace_div_element)): 403 | parent.insert(parent.index(target_element) + 1 + i, element) 404 | 405 | __exception_index += 1 406 | 407 | 408 | command_mapper = { 409 | "run": RunCommand, 410 | "execute": ExecuteCommand, 411 | "set": SetCommand, 412 | "assertEquals": AssertEqualsCommand, 413 | "assertTrue": AssertTrueCommand, 414 | "assertFalse": AssertFalseCommand, 415 | "verifyRows": VerifyRowsCommand, 416 | "echo": EchoCommand 417 | } 418 | -------------------------------------------------------------------------------- /pyconcordion2/expression_parser.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from pyparsing import Word, alphanums, ZeroOrMore, Suppress, Optional, Group 4 | 5 | # define grammar 6 | variable_name = Word(alphanums + "_").setResultsName("variable_name") 7 | property_name = Word(alphanums + "_").setResultsName("property_name") 8 | function_name = Word(alphanums + "_").setResultsName("function_name") 9 | parameter_name = Word(alphanums + "_") 10 | equals = Suppress("=") 11 | colon = Suppress(":") 12 | open_parenthesis = Suppress("(") 13 | closed_parenthesis = Suppress(")") 14 | dot = Suppress(".") 15 | comma = Suppress(",") 16 | function_definition = function_name + open_parenthesis + Group( 17 | Optional(parameter_name + ZeroOrMore(comma + parameter_name))).setResultsName("parameters") + closed_parenthesis 18 | 19 | expression = function_definition | variable_name + dot + property_name | ( 20 | variable_name + Optional((equals | colon) + function_definition)) 21 | 22 | 23 | def parse(expression_str): 24 | return expression.parseString(expression_str) 25 | 26 | 27 | def execute_within_context(context, expression_str): 28 | expression_tree = parse(expression_str) 29 | if expression_tree.function_name: 30 | fn_name = getattr(context, expression_tree.function_name) 31 | parameters = [getattr(context, parameter) for parameter in expression_tree.parameters] 32 | result = fn_name(*parameters) 33 | if expression_tree.variable_name: 34 | setattr(context, expression_tree.variable_name, result) 35 | return result 36 | elif expression_tree.variable_name: 37 | variable = getattr(context, expression_tree.variable_name) 38 | if expression_tree.property_name: 39 | _property = getattr(variable, expression_tree.property_name) 40 | return _property 41 | return variable 42 | -------------------------------------------------------------------------------- /pyconcordion2/resources/embedded.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Arial; 3 | } 4 | body { 5 | padding: 32px; 6 | } 7 | pre { 8 | padding: 6px 28px 6px 28px; 9 | background-color: #E8EEF7; 10 | } 11 | pre, pre *, code, code *, kbd { 12 | font-family: Courier New, Courier; 13 | font-size: 10pt; 14 | } 15 | h1, h1 * { 16 | font-size: 24pt; 17 | } 18 | p, td, th, li, .breadcrumbs { 19 | font-size: 10pt; 20 | } 21 | p, li { 22 | line-height: 140%; 23 | max-width: 720px; 24 | } 25 | table { 26 | border-collapse: collapse; 27 | empty-cells: show; 28 | margin: 8px 0px 8px 0px; 29 | } 30 | th, td { 31 | border: 1px solid black; 32 | padding: 3px; 33 | } 34 | td { 35 | background-color: white; 36 | vertical-align: top; 37 | } 38 | th { 39 | background-color: #C3D9FF; 40 | } 41 | li { 42 | margin-top: 6px; 43 | margin-bottom: 6px; 44 | } 45 | 46 | .example { 47 | padding: 6px 16px 6px 16px; 48 | border: 1px solid #C3D9FF; 49 | margin: 6px 0px 28px 0px; 50 | background-color: #F5F9FD; 51 | } 52 | .example h3 { 53 | margin-top: 8px; 54 | margin-bottom: 8px; 55 | font-size: 12pt; 56 | } 57 | 58 | p.success { 59 | padding: 2px; 60 | } 61 | .success, .success * { 62 | background-color: #afa !important; 63 | } 64 | .success pre { 65 | background-color: #bbffbb; 66 | } 67 | .failure, .failure * { 68 | background-color: #ffb0b0; 69 | padding: 1px; 70 | } 71 | .failure .expected { 72 | text-decoration: line-through; 73 | color: #bb5050; 74 | } 75 | .ignored, .ignored * { 76 | background-color: #f0f0f0 !important; 77 | } 78 | 79 | ins { 80 | text-decoration: none; 81 | } 82 | 83 | .exceptionMessage { 84 | background-color: #fdd; 85 | font-family: Courier New, Courier, Monospace; 86 | font-size: 10pt; 87 | display: block; 88 | font-weight: normal; 89 | padding: 4px; 90 | text-decoration: none !important; 91 | } 92 | .stackTrace, .stackTrace * { 93 | font-weight: normal; 94 | } 95 | .stackTrace { 96 | display: none; 97 | padding: 1px 4px 4px 4px; 98 | background-color: #fdd; 99 | border-top: 1px dotted black; 100 | } 101 | .stackTraceExceptionMessage { 102 | display: block; 103 | font-family: Courier New, Courier, Monospace; 104 | font-size: 8pt; 105 | white-space: wrap; 106 | padding: 1px 0px 1px 0px; 107 | } 108 | .stackTraceEntry { 109 | white-space: nowrap; 110 | font-family: Courier New, Courier, Monospace; 111 | display: block; 112 | font-size: 8pt; 113 | padding: 1px 0px 1px 32px; 114 | } 115 | .stackTraceButton { 116 | font-size: 8pt; 117 | margin: 2px 8px 2px 0px; 118 | font-weight: normal; 119 | font-family: Arial; 120 | } 121 | 122 | .special { 123 | font-style: italic; 124 | } 125 | .missing, .missing * { 126 | background-color: #ff9999; 127 | color:#bb5050; 128 | text-decoration: line-through; 129 | } 130 | .surplus, .surplus * { 131 | background-color: #ff9999; 132 | } 133 | .footer { 134 | text-align: right; 135 | margin-top: 40px; 136 | font-size: 8pt; 137 | width: 100%; 138 | color: #999; 139 | } 140 | .footer .testTime { 141 | padding: 2px 10px 0px 0px; 142 | } 143 | 144 | .idea { 145 | font-size: 9pt; 146 | color: #888; 147 | font-style: italic; 148 | } 149 | .tight li { 150 | margin-top: 1px; 151 | margin-bottom: 1px; 152 | } 153 | .commentary { 154 | float: right; 155 | width: 200px; 156 | background-color: #ffffd0; 157 | padding:8px; 158 | border: 3px solid #eeeeb0; 159 | margin: 10px 0px 10px 10px; 160 | } 161 | .commentary, .commentary * { 162 | font-size: 8pt; 163 | } -------------------------------------------------------------------------------- /pyconcordion2/resources/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $(".stackTraceButton").on("click", function(event) { 3 | $(".stackTrace." + $(this).data("exception-index")).toggle(); 4 | }) 5 | }); 6 | -------------------------------------------------------------------------------- /pyconcordion2/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Index

5 | 6 |
7 | 8 | 11 | 12 |
13 | 14 |
15 |

Examples

16 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/IndexTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | 3 | 4 | class IndexTest(ConcordionTestCase): 5 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'jjia6395' 2 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/Concordion.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 |

Concordion

17 | 18 |

19 | The Concordion framework 20 | brings together testing and specification. 21 | Concrete examples of expected behaviour (written in HTML) can be 22 | instrumented with Concordion commands that run tests against the 23 | real system via some Java fixture code. 24 | The fixture code helps to decouple the specs from the system. 25 |

26 | 27 |

28 | See here for a simple example. 29 |

30 | 31 |

Further Details

32 | 33 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/ConcordionTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | 3 | 4 | class ConcordionTest(ConcordionTestCase): 5 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/Example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Example

6 | 7 |

8 | Here's a very simple example that demonstrates the basic behaviour of Concordion. 9 | Note that the HTML specifications, below, have a Concordion namespace declaration at the top 10 | and we use the <span> tag around the variable that we're interested in checking. 11 |

12 | 13 |
14 | 15 |

16 | Assuming we have Java fixture code containing a method: 17 |

18 | 19 |
20 | public String getGreeting() {
21 |     return "Hello World!";
22 | }
23 | 
24 | 25 |
26 | 27 |
28 |

29 | When we run the following active specification it should report a 30 | success, since the 31 | expectation in the specification (Hello World!) 32 | matches the actual result of the method. 33 |

34 | 35 |
36 | <html xmlns:concordion="http://www.concordion.org/2007/concordion">
37 | <body>
38 |     <p>
39 |         The greeting should be:
40 |         <span concordion:assertEquals="greeting()">Hello World!</span>
41 |     </p>
42 | </body>
43 | </html>
44 | 
45 | 46 | 47 |
48 | 49 |
50 | 51 |

52 | On the other hand, this specfication should report a 53 | failure, since the 54 | expectation in the specification (Hello Bob!) does not match 55 | the result of the method (Hello World!). 56 |

57 | 58 |
59 | <html xmlns:concordion="http://www.concordion.org/2007/concordion">
60 | <body>
61 |     <p>
62 |         The greeting should be:
63 |         <span concordion:assertEquals="greeting()">Hello Bob!</span>
64 |     </p>
65 | </body>
66 | </html>
67 | 
68 | 69 |
70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/ExampleTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | from test_rig import TestRig 5 | 6 | 7 | class ExampleTest(ConcordionTestCase): 8 | def process(self, html): 9 | result = TestRig(fixture=self).process_html(html) 10 | return "success" if result.has_succeeded() else "failure" 11 | 12 | def greeting(self): 13 | return "Hello World!" 14 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/Command.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Commands

6 | 7 |

8 | The following commands are available: 9 |

10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/CommandTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | 5 | 6 | class CommandTest(ConcordionTestCase): 7 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/AssertEquals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

assertEquals

6 | 7 |

8 | The assertEquals command evaluates an expression and compares 9 | the result to the contents of an element in the document. The command reports 10 | a success if the evaluation result is equal to the text in the document, 11 | or a failure otherwise. The comparisons are case-sensitive. 12 |

13 | 14 |
15 | 16 |

Example

17 | 18 |

Given this instrumentation:

19 | 20 |
<span concordion:assertEquals="#user.firstName">Fred</span>
21 | 22 |

We get the following outcomes depending on the evaluation result of the expression #user.firstName:

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
Evaluation ResultOutcome
FredSUCCESS
WilmaFAILURE
fredFAILURE
42 |
43 | 44 | 45 |

Further Details

46 | 47 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/AssertEqualsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class AssertEqualsTest(ConcordionTestCase): 7 | def successOrFailure(self, snippet, outcome): 8 | t = TestRig() 9 | t.stub_result(outcome) 10 | t.process_fragment(snippet) 11 | return t.success_or_failure() 12 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/Exceptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Handling Exceptions

6 | 7 |

8 | If an exception is thrown during the evaluation of the expression, 9 | then the exception is reported. No "success" or "failure" events 10 | are reported since the exception will be treated as an implicit failure. 11 |

12 | 13 |
14 | 15 |

Example

16 | 17 |

Given the following instrumentation:

18 | 19 |
20 | <span concordion:assertEquals="myMethod()">ABCD</span>
21 | 
22 | 23 |

And given that each row in this table is an independent test, we 24 | expect these event counts: 25 |

26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
ScenarioEvent Counts
myMethod() ReturnsSuccessesFailuresExceptions
ABCD100
XYZ010
(An exception)001
57 |
58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/ExceptionsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class ExceptionsTest(ConcordionTestCase): 7 | def countsFromExecutingSnippetWithSimulatedEvaluationResult(self, snippet, outcome): 8 | t = TestRig() 9 | if outcome == "(An exception)": 10 | t.stub_result(RuntimeError()) 11 | else: 12 | t.stub_result(outcome) 13 | return t.process_fragment(snippet) 14 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/NestedActions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Nested Actions

6 | 7 |

8 | Though nested elements are supported, these 9 | elements may not contain commands. An "illegal markup" exception will be reported if 10 | any nested commands are detected. 11 |

12 | 13 |
14 | 15 |

Example

16 | 17 |

The following will result in an "illegal markup" exception being raised:

18 | 19 |
20 | <span concordion:assertEquals="#fullName">Fred 
21 |     <span concordion:set="#surname">Bloggs</span>
22 | </span> 
23 | 
24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/NestedActionsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | 4 | 5 | # TODO 6 | class NestedActionsTest(ConcordionTestCase): 7 | pass 8 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/NestedElements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Nested Elements

6 | 7 |

8 | Nested HTML elements are supported. The content of those elements is included in 9 | the expected string, but the tags themselves are not. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 | 17 |
18 | <span concordion:assertEquals="#fullName">Fred <strong>Bloggs</strong></span> 
19 | 
20 | 21 |

22 | Will match 23 | the evaluation result "Fred Bloggs". 24 |

25 |
26 | 27 |

No extra whitespace is added around the nested elements.

28 | 29 |
30 | 31 |

Example

32 | 33 |
34 | <span concordion:assertEquals="#fullName">Fred<em>Bloggs</em></span> 
35 | 
36 | 37 |

38 | 39 | Will match the string 40 | "FredBloggs", but will 41 | 42 | 43 | 44 | not match 45 | "Fred Bloggs". 46 | 47 |

48 | 49 |
50 | 51 |

Further Details

52 | 53 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/NestedElementsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class NestedElementsTest(ConcordionTestCase): 7 | def matchOrNotMatch(self, snippet, outcome): 8 | t = TestRig() 9 | t.stub_result(outcome) 10 | result = t.process_fragment(snippet) 11 | return "match" if not result.has_failed() else "not match" 12 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/SupportedElements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Supported Elements

6 | 7 |

8 | The assertEquals command can currently be used on any HTML element. 9 | There are currently no checks. 10 | 11 | However, you should avoid using the command on elements that cannot legally 12 | have a <span> child (e.g. <table>, 13 | <tr>, <ul>, <ol>). 14 |

15 | 16 |
17 | 18 |

Examples

19 | 20 |

All of these will work:

21 | 22 |
23 | <span concordion:assertEquals="#name">Fred</span>
24 | 
25 | 26 |
27 | <strong concordion:assertEquals="#name">Fred</strong>
28 | 
29 | 30 |
31 | <div concordion:assertEquals="#name">Fred</div>
32 | 
33 | 34 |
35 | <table>
36 |     <tr>
37 |         <td concordion:assertEquals="#name">Fred</td>
38 |     </tr>
39 | </table>
40 | 
41 | 42 |
43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/SupportedElementsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class SupportedElementsTest(ConcordionTestCase): 7 | def process(self, snippet): 8 | t = TestRig() 9 | t.stub_result("Fred") 10 | result = t.process_fragment(snippet) 11 | return snippet if not result.has_failed() else "Did not work" 12 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/assertEquals/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/NonString.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Non-String Results

6 | 7 |

8 | Before comparison, the evaluation result is turned into a string by 9 | calling the object's toString() method. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |
17 | <span concordion:assertEquals="myMethod()">(some expectation)</span>
18 | 
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
myMethod()
Returns
TypeThe ExpectationOutcome
1234String1234SUCCESS
1234Integer1234SUCCESS
99Integer1234FAILURE
1234Double1234FAILURE
1234Double1234.0SUCCESS
58 |
59 | 60 | 61 |

Further Details

62 | 63 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/NonStringTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class NonStringTest(ConcordionTestCase): 7 | def outcomeOfPerformingAssertEquals(self, fragment, expectedString, result, resultType): 8 | 9 | if resultType == "String": 10 | simulatedResult = result 11 | elif resultType == "Integer": 12 | simulatedResult = int(result) 13 | elif resultType == "Double": 14 | simulatedResult = float(result) 15 | else: 16 | raise RuntimeError("Unsupported result-type '{}'".format(resultType )) 17 | 18 | fragment = fragment.replace("(some expectation)", expectedString) 19 | 20 | t = TestRig() 21 | t.stub_result(simulatedResult) 22 | t.process_fragment(fragment) 23 | return t.success_or_failure() 24 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/NullResult.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

None Result

6 | 7 |

8 | If the evaluation result is None then the string 9 | "(None)" is used for performing the comparison. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |
17 | <span concordion:assertEquals="myMethod()">(some expectation)</span>
18 | 
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
myMethod()
Returns
The ExpectationOutcome
None(None)SUCCESS
NonexyzFAILURE
NoneNoneFAILURE
42 |
43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/NullResultTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class NullResultTest(ConcordionTestCase): 7 | def outcomeOfPerformingAssertEquals(self, fragment, expectedString, result): 8 | if result == "None": 9 | result = None 10 | 11 | fragment = fragment.replace("(some expectation)", expectedString) 12 | 13 | t = TestRig() 14 | t.stub_result(result) 15 | t.process_fragment(fragment) 16 | return t.success_or_failure() 17 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/VoidResult.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Void Result

6 | 7 |

8 | If an expression returns a method that returns void then 9 | the result is treated as null. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |

Given we have the following method in our fixture code:

17 | 18 |
19 | public void myVoidMethod() {
20 |     ...
21 | }
22 | 
23 | 24 |
25 |

26 | Then the following markup will cause a SUCCESS 27 | to be reported, since no return value can be obtained from myVoidMethod() 28 | the result is taken to be None: 29 |

30 | 31 |
32 | <span concordion:assertEquals="myVoidMethod()">(None)</span>
33 | 
34 | 35 |
36 | 37 |
38 |

39 | And, to demonstrate a counter-example, the following markup will report 40 | a FAILURE: 41 |

42 | 43 |
44 | <span concordion:assertEquals="myVoidMethod()">xyz</span>
45 | 
46 |
47 | 48 |
49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/VoidResultTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class VoidResultTest(ConcordionTestCase): 7 | def process(self, fragment): 8 | t = TestRig(fixture=self) 9 | t.stub_result(None) 10 | t.process_fragment(fragment) 11 | return t.success_or_failure() 12 | 13 | def myVoidMethod(self): 14 | pass 15 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/assertEquals/nonString/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/LineContinuations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Line Continuations

6 | 7 |

8 | It is sometimes useful to be able to break a long line of text 9 | over several lines without the linebreaks being treated as whitespace. 10 |

11 | 12 |

13 | You can do this by putting an underscore at the end of a line, preceded 14 | by a space. 15 |

16 | 17 |
18 | 19 |

Example

20 | 21 |

These two statements are treated identically:

22 | 23 | (1) 24 |
<pre concordion:assertEquals="#firstName">Fred Flintstone</pre>
25 | 26 | (2) 27 |
<pre concordion:assertEquals="#firstName">Fred Flint _
28 | stone</pre>
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
#firstNameSuccessFailure
Fred Flintstone(1), (2)
Fred Flint stone(1), (2)
Fred Flint _ stone(1), (2)
53 | 54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/LineContinuationsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class Result(object): 7 | def __init__(self): 8 | self._successes = [] 9 | self._failures = [] 10 | 11 | @property 12 | def successes(self): 13 | return ", ".join(self._successes) 14 | 15 | @property 16 | def failures(self): 17 | return ", ".join(self._failures) 18 | 19 | 20 | class LineContinuationsTest(ConcordionTestCase): 21 | def setUp(self): 22 | self.snippets = [] 23 | 24 | def addSnippet(self, snippet): 25 | self.snippets.append(snippet) 26 | 27 | def processSnippets(self, evaluationResult): 28 | result = Result() 29 | 30 | for i, snippet in enumerate(self.snippets, start=1): 31 | t = TestRig() 32 | t.stub_result(evaluationResult) 33 | has_failed = t.process_fragment(snippet).has_failed() 34 | 35 | if has_failed: 36 | result._failures.append("({})".format(i)) 37 | else: 38 | result._successes.append("({})".format(i)) 39 | return result 40 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/Normalization.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Normalization

6 | 7 |

Whitespace characters are:

8 | 9 | 15 | 16 |
17 | 18 |

Examples

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
Source StringNormalized
fredfred
[SPACE]fredfred
[SPACE]fred[SPACE]fred
[SPACE][SPACE]fred[LF][SPACE]fred
[SPACE][CR][LF][TAB][SPACE]fred[SPACE][SPACE]bloggsfred[SPACE]bloggs
fred[LF][SPACE][TAB][CR][LF]bloggsfred[SPACE]bloggs
fred[LF][SPACE]x[TAB][CR][LF]bloggsfred[SPACE]x[SPACE]bloggs
54 |
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/NormalizationTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from spec.concordion.command.assertEquals.whitespace.WhitespaceTest import WhitespaceTest 3 | 4 | 5 | class NormalizationTest(WhitespaceTest): 6 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/Whitespace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Whitespace

6 | 7 |

8 | Whitespace for an assertEquals is treated in roughly the 9 | same way that web browsers treat it. 10 |

11 | 12 |

13 | Whitespace at the start and end of the string is removed before comparison 14 | and whitespace in the middle of a string is compressed so that multiple 15 | whitespace characters become a single space character. The same "normalization" 16 | is performed on both the text read from the document and the string result 17 | of the evaluation. 18 |

19 | 20 |
21 | 22 |

Example

23 | 24 |
<span concordion:assertEquals="#firstName">Fred Flintstone</span>
25 | 26 |
<span concordion:assertEquals="#firstName">  Fred 
27 |         Flintstone
28 | </span>
29 | 30 |

31 | If #firstName returns "Fred Flintstone", 32 | both 33 | the above statements will report a success. 34 |

35 | 36 |

37 | If #firstName returns " Fred Flintstone ", 38 | both 39 | the above statements will report a success. 40 |

41 | 42 |

43 | If #firstName returns "Wilma Flintstone", 44 | both 45 | the above statements will report a failure. 46 |

47 | 48 |
49 | 50 |

Further Details

51 | 52 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/WhitespaceTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from commands import normalize 3 | from base import ConcordionTestCase 4 | from test_rig import TestRig 5 | 6 | 7 | white_space_mapper = { 8 | "[SPACE]": " ", 9 | "[TAB]": "\t", 10 | "[LF]": "\n", 11 | "[CR]": "\r", 12 | } 13 | 14 | 15 | class WhitespaceTest(ConcordionTestCase): 16 | def whichSnippetsSucceed(self, snippet1, snippet2, evaluationResult): 17 | return self.which(self.succeeds(snippet1, evaluationResult), self.succeeds(snippet2, evaluationResult)) 18 | 19 | def whichSnippetsFail(self, snippet1, snippet2, evaluationResult): 20 | return self.which(self.fails(snippet1, evaluationResult), self.fails(snippet2, evaluationResult)) 21 | 22 | def which(self, b1, b2): 23 | if b1 and b2: 24 | return "both" 25 | elif b1: 26 | return "the first of" 27 | elif b2: 28 | return "the second of" 29 | return "neither" 30 | 31 | def fails(self, snippet, evaluationResult): 32 | return not self.succeeds(snippet, evaluationResult) 33 | 34 | def succeeds(self, snippet, evaluationResult): 35 | t = TestRig() 36 | t.stub_result(evaluationResult) 37 | return t.process_fragment(snippet).has_succeeded() 38 | 39 | def normalize(self, s): 40 | for key, value in white_space_mapper.items(): 41 | s = s.replace(key, value) 42 | s = normalize(s) 43 | return s.replace(" ", "[SPACE]") 44 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/assertEquals/whitespace/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertFalse/AssertFalse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

assertFalse

6 | 7 |

8 | The concordion:assertFalse tag evaluates an expression 9 | to obtain a boolean result. If the result is true, the tagged element 10 | is marked as a success else it is marked as a failure. 11 |

12 | 13 |
14 | 15 |

Example

16 | 17 |

Given this instrumented snippet:

18 | 19 |
20 | <p>
21 |     Since the password was incorrect, the user should not be        
22 |     <span concordion:assertFalse="#user.isLoggedIn()">logged in</span>.
23 | </p>    
24 | 
25 | 26 |

We get the following outcomes depending on the evaluation result of the expression #user.isLoggedIn():

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
Evaluation ResultOutcome
trueFAILURE
falseSUCCESS
42 | 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertFalse/AssertFalseTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | from test_rig import text_to_bool 5 | 6 | 7 | class AssertFalseTest(ConcordionTestCase): 8 | def successOrFailure(self, snippet, outcome): 9 | t = TestRig() 10 | t.stub_result(text_to_bool(outcome)) 11 | t.process_fragment(snippet) 12 | return t.success_or_failure() 13 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertFalse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/assertFalse/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertTrue/AssertTrue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

assertTrue

6 | 7 |

8 | The concordion:assertTrue tag evaluates an expression 9 | to obtain a boolean result. If the result is true, the tagged element 10 | is marked as a success else it is marked as a failure. 11 |

12 | 13 |
14 | 15 |

Example

16 | 17 |

Given this instrumented snippet:

18 | 19 |
20 | <p>
21 |     Since the password was correct, the user should then be        
22 |     <span concordion:assertTrue="#user.isLoggedIn()">logged in</span>.
23 | </p>    
24 | 
25 | 26 |

We get the following outcomes depending on the evaluation result of the expression #user.isLoggedIn():

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
Evaluation ResultOutcome
trueSUCCESS
falseFAILURE
42 | 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertTrue/AssertTrueTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig, text_to_bool 4 | 5 | 6 | class AssertTrueTest(ConcordionTestCase): 7 | def successOrFailure(self, snippet, outcome): 8 | t = TestRig() 9 | t.stub_result(text_to_bool(outcome)) 10 | t.process_fragment(snippet) 11 | return t.success_or_failure() 12 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/assertTrue/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/assertTrue/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/echo/DisplayingNulls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Displaying Nones

6 | 7 |

8 | Nones are wrapped in <em> tags (i.e. <em>None</em>) to 9 | make a visual distinction between the string "None" and a None value. 10 |

11 | 12 |

13 | The value is appended to any child markup. 14 |

15 | 16 |
17 | 18 |

Examples

19 | 20 |

21 | Given the expression "username" returns a None, we expect the following results: 22 |

23 | 24 |

25 | We have stripped out the concordion:echo attributes from the 26 | "expected output" for legibility. They may be present. 27 |

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
InstrumentationExpected output
<span concordion:echo="username" />
<span><em>None</em></span>
<span concordion:echo="username"></span>
<span><em>None</em></span>
<span concordion:echo="username">abc</span>
<span>abc<em>None</em></span>
<span concordion:echo="username"><b>abc</b></span>
<span><b>abc</b><em>None</em></span>
51 | 52 |
53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/echo/DisplayingNullsTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | from test_rig import TestRig 3 | 4 | 5 | class DisplayingNullsTest(ConcordionTestCase): 6 | def render(self, fragment): 7 | rig = TestRig() 8 | rig.stub_result(None) 9 | rig.process_fragment(fragment) 10 | result = rig.get_output_fragment_xml() 11 | return result.replace(" concordion:echo=\"username\">", ">") 12 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/echo/Echo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

echo

6 | 7 |

8 | The concordion:echo tag evaluates an expression and inserts 9 | the result into the output HTML. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |

17 | If the expression "username" 18 | evaluates to "jbloggs" and we 19 | have the following instrumentation in our specification: 20 |

21 | 22 |
23 | <p>
24 |     Username: <span concordion:echo="username" />
25 | </p>    
26 | 
27 | 28 |

Then we expect the following output:

29 | 30 |
31 | <p>
32 |     Username: <span concordion:echo="username">jbloggs</span>
33 | </p>    
34 | 
35 | 36 |
37 | 38 |

Further Details

39 | 40 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/echo/EchoTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | from test_rig import TestRig 3 | 4 | 5 | class EchoTest(ConcordionTestCase): 6 | nextResult = None 7 | 8 | def setNextResult(self, result): 9 | self.nextResult = result 10 | 11 | def render(self, fragment): 12 | rig = TestRig() 13 | rig.stub_result(self.nextResult) 14 | rig.process_fragment(fragment) 15 | return rig.get_output_fragment_xml() 16 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/echo/EscapingHtmlCharacters.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Escaping HTML Characters

6 | 7 |

8 | Special characters <, > and & are escaped in the output. 9 |

10 | 11 |
12 | 13 |

Examples

14 | 15 |

16 | Given this instrumentation: 17 |

18 |
<span concordion:echo="username" />
19 | 20 |

21 | We expect the following outputs, depending on the evaluation result of username: 22 |

23 | 24 |

25 | We have stripped out the concordion:echo attributes from the 26 | "expected output" for legibility. They may be present. 27 |

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
usernameExpected output
abc
<span>abc</span>
a&b
<span>a&amp;b</span>
a<bc
<span>a&lt;bc</span>
<&>abc
<span>&lt;&amp;&gt;abc</span>
51 | 52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/echo/EscapingHtmlCharactersTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | from test_rig import TestRig 3 | 4 | 5 | class EscapingHtmlCharactersTest(ConcordionTestCase): 6 | def render(self, fragment, evalResult): 7 | rig = TestRig() 8 | rig.stub_result(evalResult) 9 | rig.process_fragment(fragment) 10 | result = rig.get_output_fragment_xml() 11 | return result.replace(" concordion:echo=\"username\">", ">") 12 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/echo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/echo/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/execute/Execute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "execute" 5 | 6 | 7 | 8 |

execute

9 | 10 |

11 | An execute command lets you run a method in the 12 | Java fixture code. 13 |

14 | 15 |
16 | 17 |

Example

18 | 19 |

The following instrumentation:

20 |
21 | <p concordion:execute="myMethod()">Some text goes here.</p>
22 | 
23 |

24 | Will 25 | call myMethod() in the Java fixture code. 26 |

27 | 28 |
29 | 30 |

Further Details

31 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/execute/ExecuteTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | from test_rig import TestRig 3 | 4 | 5 | class ExecuteTest(ConcordionTestCase): 6 | method_called = False 7 | 8 | def myMethodWasCalledProcessing(self, fragment): 9 | t = TestRig(fixture=self) 10 | t.process_fragment(fragment) 11 | return "Will" if self.method_called else "Will Not" 12 | 13 | def myMethod(self): 14 | self.method_called = True 15 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/execute/ExecutingTables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Executing Tables

6 | 7 |

8 | The execute command has special behaviour when placed on 9 | a <table> element. Instead of executing once, it 10 | executes every detail row in the table and transfers the commands 11 | from the header row to each detail row. 12 |

13 | 14 |
15 | 16 |

Example

17 | 18 |
19 | <table concordion:execute="#username = generateUsername(#fullName)">
20 |     <tr>
21 |         <th concordion:set="#fullName">Full Name</th>
22 |         <th concordion:assertEquals="#username">Username</th>
23 |     </tr>
24 |     <tr>
25 |         <td>Fred Bloggs</td>
26 |         <td>fredbloggs</td>
27 |     </tr>
28 |     <tr>
29 |         <td>John Doe</td>
30 |         <td>johndoe</td>
31 |     </tr>
32 |     <tr>
33 |         <td>Winston Churchill</td>
34 |         <td>winston</td>
35 |     </tr>
36 | </table>
37 | 
38 | 39 |

40 | If the method generateUsername() returns the 41 | full name in lowercase with spaces removed, when we run 42 | the test we expect: 43 | 2 successes and 44 | 1 failure and 45 | 0 exceptions 46 | to be reported. 47 | 48 | The failure will have an expected value of 49 | "winston" 50 | and an actual value of 51 | "winstonchurchill". 52 |

53 | 54 |
55 | 56 |

Further Details

57 | 58 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/execute/ExecutingTablesTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | from test_rig import TestRig 3 | 4 | 5 | class ExecutingTablesTest(ConcordionTestCase): 6 | def process(self, fragment): 7 | r = TestRig(fixture=self).process_fragment(fragment) 8 | 9 | result = Result() 10 | result.successCount = r.num_success 11 | result.failureCount = r.num_failure 12 | result.exceptionCount = r.num_exception 13 | 14 | lastEvent = r.last_failed_event() 15 | if lastEvent is not None: 16 | result.lastActualValue = lastEvent.actual 17 | result.lastExpectedValue = lastEvent.expected 18 | 19 | return result 20 | 21 | def generateUsername(self, fullName): 22 | return fullName.replace(" ", "").lower() 23 | 24 | 25 | class Result(object): 26 | successCount = None 27 | failureCount = None 28 | exceptionCount = None 29 | lastExpectedValue = None 30 | lastActualValue = None 31 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/execute/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/execute/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/results/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/contentType/ContentType.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Content Type

8 | 9 |

10 | If the document <head> section does not contain content-type metadata, then a <meta> element will be automatically inserted, 11 | specifying a content-type with charset set to UTF-8. 12 |

13 | 14 |
15 | 16 |

Example

17 | 18 |

When this document is processed:

19 |
20 | <html xmlns:concordion="http://www.concordion.org/2007/concordion">
21 | <head>
22 | <title>My Title</title>
23 | </head>
24 | <body/>
25 | </html>
26 | 
27 | 28 |

, the following output will be produced:

29 | 30 |
31 | <html xmlns:concordion="http://www.concordion.org/2007/concordion">
32 | <head>
33 | <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
34 | <title>My Title</title>
35 | </head>
36 | <body/>
37 | </html>
38 | 
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/contentType/ContentTypeTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from lxml import etree 4 | 5 | from base import ConcordionTestCase 6 | from test_rig import TestRig 7 | 8 | 9 | class ContentTypeTest(ConcordionTestCase): 10 | def process(self, html): 11 | result = TestRig().process_html(html) 12 | self.remove_irrelevant_elements(result.root_element) 13 | return etree.tostring(result.root_element) 14 | 15 | def remove_irrelevant_elements(self, root_element): 16 | self.remove_irrelevant_stylesheet(root_element) 17 | self.remove_script_elements(root_element) 18 | 19 | def remove_script_elements(self, root_element): 20 | for script in root_element.xpath("//script"): 21 | script.getparent().remove(script) 22 | 23 | def remove_irrelevant_stylesheet(self, root_element): 24 | head = root_element.xpath("//head")[0] 25 | style = head.xpath("//style")[0] 26 | head.remove(style) 27 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/contentType/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/results/contentType/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/stylesheet/MissingHeadElement.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Missing <head>

8 | 9 |

10 | If the document does not contain a <head> section then one will 11 | be automatically inserted as the first child of the <html> element. 12 | Any elements before the <body> section will be moved into the 13 | <head>. 14 |

15 | 16 |
17 | 18 |

Example

19 | 20 |

When this document is processed:

21 |
22 | <html xmlns:concordion="http://www.concordion.org/2007/concordion">
23 | <link href="my.css" rel="stylesheet" type="text/css" />
24 | <title>My Title</title>
25 | <body>
26 |     <p>Body content goes here.</p>
27 | </body>
28 | </html>
29 | 
30 | 31 |

, a <head> element will be inserted:

32 | 33 |
34 | <html xmlns:concordion="http://www.concordion.org/2007/concordion">
35 | <head>
36 | <link href="my.css" rel="stylesheet" type="text/css"/>
37 | <title>My Title</title>
38 | </head>
39 | <body>
40 |     <p>Body content goes here.</p>
41 | </body>
42 | </html>
43 | 
44 |
45 | 46 |

Further Details

47 | 48 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/stylesheet/MissingHeadElementTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from lxml import etree 4 | 5 | from base import ConcordionTestCase 6 | from test_rig import TestRig 7 | 8 | 9 | class MissingHeadElementTest(ConcordionTestCase): 10 | def process(self, html): 11 | result = TestRig().process_html(html) 12 | self.remove_irrelevant_elements(result.root_element) 13 | return etree.tostring(result.root_element) 14 | 15 | def remove_irrelevant_elements(self, root_element): 16 | self.remove_irrelevant_stylesheet(root_element) 17 | self.remove_script_elements(root_element) 18 | self.remove_meta(root_element) 19 | 20 | def remove_script_elements(self, root_element): 21 | for script in root_element.xpath("//script"): 22 | script.getparent().remove(script) 23 | 24 | def remove_irrelevant_stylesheet(self, root_element): 25 | head = root_element.xpath("//head")[0] 26 | style = head.xpath("//style")[0] 27 | head.remove(style) 28 | 29 | def remove_meta(self, root_element): 30 | for meta in root_element.xpath("//meta"): 31 | meta.getparent().remove(meta) 32 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/stylesheet/Stylesheet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 |

Embedded Stylesheet

10 | 11 |

12 | The output documents need some CSS to render 13 | successes, failures, stack traces etc. correctly. 14 |

15 | 16 |

17 | The essential styles will be embedded at the top of the 18 | <head> section of the document. This allows 19 | user-supplied stylesheets to override the settings, 20 | if desired, and allows the document to perform 21 | its function without any external dependencies 22 | (this makes it easier to e-mail the output documents). 23 |

24 | 25 |
26 | 27 |

Example

28 | 29 |

If we process this document:

30 |
31 | <html xmlns:concordion="http://www.concordion.org/2007/concordion">
32 | <head>
33 |     <title>Example</title>
34 | </head>
35 | <body>
36 |     <p>Body content goes here.</p>
37 | </body>
38 | </html>
39 | 
40 | 41 |

42 | We expect the output document to have a 43 | <style> 44 | element inserted into the 45 | <head> section 46 | before 47 | the <title> element. 48 |

49 | 50 |

51 | The <style> element 52 | should 53 | contain styles for CSS classes like 54 | ".success" and 55 | ".failure". 56 |

57 |
58 | 59 |

Further Details

60 | 61 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/stylesheet/StylesheetTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from lxml import etree 4 | 5 | from base import ConcordionTestCase 6 | from test_rig import TestRig 7 | 8 | 9 | class StylesheetTest(ConcordionTestCase): 10 | def processDocument(self, html): 11 | self.rig = TestRig() 12 | result = self.rig.process_html(html) 13 | self.root_element = result.root_element 14 | 15 | def getRelativePosition(self, outer, target, sibling): 16 | outer_tag = self.root_element.xpath("//{}".format(outer))[0] 17 | 18 | target_index = 0 19 | sibling_index = 0 20 | for i, children in enumerate(outer_tag.getchildren()): 21 | if children.tag == target: 22 | target_index = i 23 | elif children.tag == sibling: 24 | sibling_index = i 25 | 26 | return "after" if target_index > sibling_index else "before" 27 | 28 | def elementTextContains(self, elementName, s1, s2): 29 | element_tag = self.root_element.xpath("//{}".format(elementName))[0] 30 | 31 | element_text = etree.tostring(element_tag) 32 | result = s1 in element_text and s2 in element_text 33 | return "should" if result else "should not" 34 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/results/stylesheet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/results/stylesheet/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/run/Run.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "run" 5 | 6 | 7 | 8 |

run

9 | 10 |

11 | The run command lets you run another test from this 12 | test, in a similar way to JUnit test-suites. This can be a useful 13 | way to view progress on a set of acceptance tests for a story. 14 |

15 | 16 |

17 | The format is: 18 |

19 |
20 | <a concordion:run="runner-name" href="relative-link">some link text</a>
21 | 22 |

23 | The runner-name should normally be "concordion". However, it 24 | is possible to implement your own runners to run tests implemented in another tool. 25 |

26 | 27 |
28 | 29 |

Example

30 | 31 |

Here we run the test for the 32 | set command 33 | using this HTML:

34 |
35 | <a concordion:run="concordion" href="../set/Set.html">set command</a>
36 | 37 |
38 | 39 |

Further Details

40 | 41 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/run/RunTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | 5 | 6 | class RunTest(ConcordionTestCase): 7 | pass 8 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/run/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/run/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/set/Set.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | "set" 5 | 6 | 7 | 8 |

set

9 | 10 |

11 | A set command sets a temporary variable to the 12 | text contents of the instrumented element, so that it can 13 | be referenced by another command. 14 |

15 | 16 |
17 | 18 |

Example

19 | 20 |

The following instrumentation:

21 |
22 | <p>
23 |     My name is <b concordion:set="#fullName">David Peterson</b>.
24 |     
25 |     <span concordion:execute="setUpUser(#fullName)" />
26 | </p>
27 | 
28 |

29 | Calls the method setUpUser() with the 30 | string value 31 | David Peterson. 32 |

33 |
34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/set/SetTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | from test_rig import TestRig 3 | 4 | 5 | class SetTest(ConcordionTestCase): 6 | param = None 7 | 8 | def process(self, snippet): 9 | t = TestRig(self) 10 | return t.process_fragment(snippet) 11 | 12 | def getParameterPassedIn(self): 13 | return self.param 14 | 15 | def setUpUser(self, full_name): 16 | self.param = full_name 17 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/set/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/set/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/TableBodySupport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Support for 'thead' and 'tbody'

8 | 9 |

10 | Although web browsers (and even the XHTML 1.0 Strict DTD) accept HTML tables without 11 | explicit <thead> and<tbody> sections, 12 | some people prefer to put them in. The verifyRows 13 | command supports tables containing these sections. 14 |

15 | 16 |
17 | 18 |

Example

19 | 20 |

Given the method getNames() returns a Collection containing the names: 21 | John, Paul 22 |

23 | 24 |

The following instrumentation:

25 | 26 |
27 | <table concordion:verifyRows="#name : getNames()">
28 |     <thead>
29 |         <tr>
30 |             <th concordion:assertEquals="#name">Name</th>
31 |         </tr>
32 |     </thead>
33 |     <tbody>
34 |         <tr>
35 |             <td>John</td>
36 |         </tr>
37 |     </tbody>
38 | </table>
39 | 
40 | 41 |

Results in this output:

42 | 43 |
44 | <table concordion:verifyRows="#name : getNames()">
45 |     <thead>
46 |         <tr>
47 |             <th concordion:assertEquals="#name">Name</th>
48 |         </tr>
49 |     </thead>
50 |     <tbody>
51 |         <tr>
52 |             <td class="success">John</td>
53 |         </tr>
54 |     <tr class="surplus"> _
55 | <td class="failure"><del class="expected">&#160;</del>
56 | <ins class="actual">Paul</ins></td> _
57 | </tr> _
58 | </tbody>
59 | </table>
60 | 
61 | 62 |

Notice how the surplus row is added into the tbody section.

63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/TableBodySupportTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | from test_rig import TestRig 5 | 6 | 7 | class TableBodySupportTest(ConcordionTestCase): 8 | def setUp(self): 9 | self.names = [] 10 | 11 | def setUpNames(self, namesAsCSV): 12 | self.names = namesAsCSV.split(", ") 13 | 14 | def getNames(self): 15 | return self.names 16 | 17 | def process(self, inputFragment): 18 | test_rig = TestRig(fixture=self) 19 | test_rig.process_fragment(inputFragment) 20 | return test_rig.get_output_fragment_xml() 21 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/VerifyRows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

verifyRows

9 | 10 |

11 | When used on a <table>, the verifyRows 12 | command compares the contents of the table with the contents of a collection, 13 | and reports similarities, differences and missing or surplus rows. 14 |

15 | 16 |

17 | The expression must return something Iterable (e.g. a Collection) 18 | and the contents of the table must be in the same order as the collection. 19 |

20 | 21 |
22 | 23 |

Example

24 | 25 |
26 | <table concordion:verifyRows="#username : usernames()">
27 |     <tr>
28 |         <th concordion:assertEquals="#username">Username</th> 
29 |     </tr>
30 |     <tr>
31 |         <td>bpeep</td> 
32 |     </tr>
33 |     <tr>
34 |         <td>jspratt</td> 
35 |     </tr>
36 | </table>
37 | 
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
usernames CollectionResults in Rows Marked As
bpeep, jsprattSUCCESS, SUCCESS
jspratt, bpeepFAILURE, FAILURE
hdumpty, jsprattFAILURE, SUCCESS
bpeepSUCCESS, MISSING
bpeep, jspratt, ppanSUCCESS, SUCCESS, SURPLUS
rhood, jspratt, ppan, mdawFAILURE, SUCCESS, SURPLUS, SURPLUS
69 | 70 |
71 | 72 | 73 |

Further Details

74 | 75 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/VerifyRowsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | from test_rig import TestRig 5 | 6 | 7 | class VerifyRowsTest(ConcordionTestCase): 8 | _usernames = [] 9 | 10 | def processFragment(self, fragment, csv): 11 | self._usernames = [username.strip() for username in csv.split(",")] 12 | result = TestRig(fixture=self).process_fragment(fragment) 13 | return self.categorize(result) 14 | 15 | def categorize(self, result): 16 | css_classes = [] 17 | table = result.root_element.xpath("//table")[0] 18 | for row in table.xpath(".//tr"): 19 | css_class = row.attrib.get("class") 20 | if not css_class: 21 | try: 22 | cell = row.xpath("td")[0] 23 | except: 24 | continue 25 | css_class = cell.attrib.get("class") 26 | if css_class: 27 | css_classes.append(css_class.upper()) 28 | return ", ".join(css_classes) 29 | 30 | def usernames(self): 31 | return self._usernames -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/verifyRows/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/results/MissingRows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Missing Rows

8 | 9 |

10 | Missing rows are marked with CSS class="missing" 11 | on the <tr> element. 12 |

13 | 14 |

15 | In the example, below, we will also demonstrate how the verifyRows 16 | command can be used to check multiple properties of objects in a collection. 17 |

18 | 19 |
20 | 21 |

Example

22 | 23 |

Given a method getPeople() that returns a Collection containing the following Person objects:

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
First NameLast NameBirth Year
JohnTravolta1954
CliffRichard1940
BritneySpears1981
47 | 48 | 49 |

And the following instrumentation:

50 | 51 |
 52 | <table concordion:verifyRows="#person : getPeople()">
 53 |     <tr>
 54 |         <th concordion:assertEquals="#person.firstName">First Name</th>
 55 |         <th concordion:assertEquals="#person.lastName">Last Name</th>
 56 |         <th concordion:assertEquals="#person.birthYear">Birth Year</th>
 57 |     </tr>
 58 |     <tr>
 59 |         <td>John</td>
 60 |         <td>Travolta</td>
 61 |         <td>1066</td>
 62 |     </tr>
 63 |     <tr>
 64 |         <td>Michael</td>
 65 |         <td>Jackson</td>
 66 |         <td>1958</td>
 67 |     </tr>
 68 |     <tr>
 69 |         <td>Britney</td>
 70 |         <td>Spears</td>
 71 |         <td>1981</td>
 72 |     </tr>
 73 |     <tr>
 74 |         <td>Mick</td>
 75 |         <td>Jagger</td>
 76 |         <td>1943</td>
 77 |     </tr>
 78 | </table>
 79 | 
80 | 81 |

Results in this output:

82 | 83 |
 84 | <table concordion:verifyRows="#person : getPeople()">
 85 |     <tr>
 86 |         <th concordion:assertEquals="#person.firstName">First Name</th>
 87 |         <th concordion:assertEquals="#person.lastName">Last Name</th>
 88 |         <th concordion:assertEquals="#person.birthYear">Birth Year</th>
 89 |     </tr>
 90 |     <tr>
 91 |         <td class="success">John</td>
 92 |         <td class="success">Travolta</td>
 93 |         <td class="failure"><del class="expected">1066</del>
 94 | <ins class="actual">1954</ins></td>
 95 |     </tr>
 96 |     <tr>
 97 |         <td class="failure"><del class="expected">Michael</del>
 98 | <ins class="actual">Cliff</ins></td>
 99 |         <td class="failure"><del class="expected">Jackson</del>
100 | <ins class="actual">Richard</ins></td>
101 |         <td class="failure"><del class="expected">1958</del>
102 | <ins class="actual">1940</ins></td>
103 |     </tr>
104 |     <tr>
105 |         <td class="success">Britney</td>
106 |         <td class="success">Spears</td>
107 |         <td class="success">1981</td>
108 |     </tr>
109 |     <tr class="missing">
110 |         <td>Mick</td>
111 |         <td>Jagger</td>
112 |         <td>1943</td>
113 |     </tr>
114 | </table>
115 | 
116 | 117 |

118 | Notice that the Mick Jagger item was expected to be in the collection, 119 | but was not, so the row is marked with class="missing". 120 |

121 | 122 |
123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/results/MissingRowsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | from test_rig import TestRig 5 | 6 | 7 | class MissingRowsTest(ConcordionTestCase): 8 | def setUp(self): 9 | self.people = [] 10 | 11 | def addPerson(self, firstName, lastName, birthYear): 12 | self.people.append(Person(firstName, lastName, birthYear)) 13 | 14 | def getOutputFragment(self, inputFragment): 15 | test_rig = TestRig(fixture=self) 16 | test_rig.process_fragment(inputFragment) 17 | return test_rig.get_output_fragment_xml() 18 | 19 | def getPeople(self): 20 | return self.people 21 | 22 | 23 | class Person(object): 24 | def __init__(self, firstName, lastName, birthYear): 25 | self.firstName = firstName 26 | self.lastName = lastName 27 | self.birthYear = birthYear 28 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/results/SurplusRows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Surplus Rows

8 | 9 |

10 | If the collection contains more objects than expected, extra rows are 11 | added to the table. These rows are marked with CSS class="surplus" 12 | on the <tr> element. 13 |

14 | 15 | 16 |
17 | 18 |

Example

19 | 20 |

Given a method getPeople() that returns a Collection containing the following Person objects:

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
First NameLast Name
JohnTravolta
CliffRichard
36 | 37 | 38 |

And the following instrumentation:

39 | 40 |
41 | <table concordion:verifyRows="#person : getPeople()">
42 |     <tr>
43 |         <th concordion:assertEquals="#person.firstName">First Name</th>
44 |         <th concordion:assertEquals="#person.lastName">Last Name</th>
45 |     </tr>
46 |     <tr>
47 |         <td>John</td>
48 |         <td>Travolta</td>
49 |     </tr>
50 | </table>
51 | 
52 | 53 |

Results in this output:

54 | 55 |
56 | <table concordion:verifyRows="#person : getPeople()">
57 |     <tr>
58 |         <th concordion:assertEquals="#person.firstName">First Name</th>
59 |         <th concordion:assertEquals="#person.lastName">Last Name</th>
60 |     </tr>
61 |     <tr>
62 |         <td class="success">John</td>
63 |         <td class="success">Travolta</td>
64 |     </tr>
65 |     <tr class="surplus"> _
66 | <td class="failure"><del class="expected">&#160;</del>
67 | <ins class="actual">Cliff</ins></td> _
68 | <td class="failure"><del class="expected">&#160;</del>
69 | <ins class="actual">Richard</ins></td> _
70 | </tr> _
71 | </table>
72 | 
73 | 74 |
75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/results/SurplusRowsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from spec.concordion.command.verifyRows.results.MissingRowsTest import MissingRowsTest 4 | 5 | 6 | class SurplusRowsTest(MissingRowsTest): 7 | def addPerson(self, firstName, lastName): 8 | super(SurplusRowsTest, self).addPerson(firstName, lastName, 1973) 9 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/command/verifyRows/results/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/command/verifyRows/results/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/CSSExtension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

CSS Extension

6 | 7 |

8 | CSS resources can be added to the Concordion output using an extension. 9 | The CSS can either be: 10 |

    11 |
  1. embedded in the Concordion output HTML, or
  2. 12 |
  3. linked, in which case the CSS file will be copied to the specified location in the Concordion output folder and a CSS declaration added to the output HTML.
  4. 13 |
14 |

15 | 16 |
17 |

Example - embedded CSS

18 | 19 |

An extension with embedded CSS is installed.

20 |

When Concordion is run, 21 | the CSS is embedded in the <head> section of the output HTML.

22 |
23 | 24 |
25 |

Example - linked CSS

26 | 27 |

An extension with linked CSS is installed for the CSS file my.css with the target location /css/my.css.

28 |

When Concordion is run, 29 | the resource /css/my.css is available in the Concordion output directory, 30 | and a link to the external stylesheet css/my.css is declared in the output HTML.

31 |
32 | 33 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/CSSExtensionTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | 4 | 5 | class CSSExtensionTest(ConcordionTestCase): 6 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/CustomCommand.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Custom Commands

6 | 7 |

8 | Users can add their own commands to Concordion as extensions. User-contributed commands must use their own namespace that must not contain concordion.org. 9 | Custom commands are automatically wrapped with a class that will notify ThrowableCaughtListeners of any Throwables that are thrown by the command. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |

An extension is installed that adds the log command in the http://myorg.org/my/extension namespace. This command simply logs the element text.

17 |

Running a specification containing:

18 |
19 | <div xmlns:myext="http://myorg.org/my/extension">
20 | <p myext:log="">The answer is 42</p>
21 | </div>
22 | 
23 | 
24 | 25 |

logs: 26 |

27 | 28 | 29 | 30 |
Output
The answer is 42
31 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/CustomCommandTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | 4 | 5 | class CustomCommandTest(ConcordionTestCase): 6 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/Extension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Extension

6 | 7 |

8 | A Concordion extension introduces additional functionality to Concordion. Extensions are able to: 9 |

10 | 11 | 17 | 18 |

Further Questions

19 | 22 | 23 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/ExtensionConfiguration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Extension Configuration

6 | 7 |

8 | Extensions can be added to Concordion using an annotation in the fixture class and/or using a system property. 9 |

10 | 11 |

Annotations

12 | 13 |

@Extension

14 |

Within a fixture class, fields that are annotated with @org.concordion.api.extension.Extension will be 15 | added to Concordion as extensions.

16 | 17 |

Fields with this annotation must be public and must implement org.concordion.api.extension.ConcordionExtension.

18 | 19 |
20 |

Example - Extensions fields

21 |

Executing the following fixture:

22 |
 23 | import org.concordion.integration.junit4.ConcordionRunner;
 24 | import org.junit.runner.RunWith;
 25 | import org.concordion.api.extension.Extension;
 26 | import org.concordion.api.extension.ConcordionExtension;
 27 | import test.concordion.extension.fake.*;
 28 |  
 29 | @RunWith(ConcordionRunner.class)
 30 | public class ExampleFixture {
 31 |     
 32 |     @Extension
 33 |     public ConcordionExtension extension = new FakeExtension1();
 34 |  
 35 |     @Extension
 36 |     public FakeExtension2 extension2 = new FakeExtension2();
 37 | }    
 38 |     
39 |

will install both extensions FakeExtension1, FakeExtension2.

40 |
41 | 42 |

Extensions will be loaded from the fixture class and any of its superclasses in parent-first order. A common pattern is to have the extensions defined in a "base fixture".

43 | 44 | 45 |
46 |

Example - Extension fields in superclass

47 |

Executing the following fixture:

48 |
 49 | import org.concordion.integration.junit4.ConcordionRunner;
 50 | import org.junit.runner.RunWith;
 51 | import org.concordion.api.extension.Extension;
 52 | import org.concordion.api.extension.ConcordionExtension;
 53 | import test.concordion.extension.fake.*;
 54 |  
 55 | @RunWith(ConcordionRunner.class)
 56 | public class ExampleFixture extends BaseFixture {
 57 |     @Extension
 58 |     public ConcordionExtension extension = new FakeExtension1("ExampleExtension");
 59 |  
 60 | }    
 61 |     
62 |

which has superclass

63 |
 64 | import org.concordion.integration.junit4.ConcordionRunner;
 65 | import org.junit.runner.RunWith;
 66 | import org.concordion.api.extension.Extension;
 67 | import org.concordion.api.extension.ConcordionExtension;
 68 | import test.concordion.extension.fake.*;
 69 |  
 70 | @RunWith(ConcordionRunner.class)
 71 | public class BaseFixture {
 72 |     
 73 |     @Extension
 74 |     public FakeExtension2 extension2 = new FakeExtension2("SuperExtension");
 75 | }    
 76 |     
77 |

will install both the extensions initialised with parameters SuperExtension, ExampleExtension.

78 |
79 | 80 |

@Extensions

81 |

As an alternative, extensions that require no state from the fixture can be defined statically on the fixture class with the @org.concordion.api.extension.Extensions 82 | annotation. This annotation is parameterised with a list of the extension, or extension factory, classes to be installed.

83 | 84 |

85 | Extensions must implement org.concordion.api.extension.ConcordionExtension. 86 | Extension factories must implement org.concordion.api.extension.ConcordionExtensionFactory. 87 |

88 | 89 | 90 |
91 |

Example - Extensions

92 |

Executing the following fixture:

93 |
 94 | import org.concordion.integration.junit4.ConcordionRunner;
 95 | import org.junit.runner.RunWith;
 96 | import org.concordion.api.extension.Extensions;
 97 | import org.concordion.api.extension.ConcordionExtension;
 98 | import test.concordion.extension.fake.*;
 99 |  
100 | @RunWith(ConcordionRunner.class)
101 | @Extensions({FakeExtension1.class, FakeExtension2Factory.class})
102 | public class ExampleFixture {
103 |     
104 | }
105 |     
106 |

will install both extensions FakeExtension1, FakeExtension2FromFactory.

107 |
108 | 109 |

Extensions will be loaded from the fixture class and any of its superclasses in parent-first order. A common pattern is to have the extensions defined in a "base fixture".

110 | 111 | 112 |
113 |

Example - Extensions from superclass are loaded

114 |

Executing the following fixture:

115 |
116 | import org.junit.runner.RunWith;
117 | import org.concordion.integration.junit4.ConcordionRunner;
118 | import org.concordion.api.extension.Extensions;
119 | import org.concordion.api.extension.ConcordionExtension;
120 | import test.concordion.extension.fake.*;
121 |  
122 | @RunWith(ConcordionRunner.class)
123 | public class ExampleFixture extends BaseFixture {
124 | 
125 | }    
126 |     
127 | 128 |

which has superclass

129 |
130 | import org.junit.runner.RunWith;
131 | import org.concordion.integration.junit4.ConcordionRunner;
132 | import org.concordion.api.extension.Extensions;
133 | import org.concordion.api.extension.ConcordionExtension;
134 | import test.concordion.extension.fake.*;
135 |  
136 | @RunWith(ConcordionRunner.class) 
137 | @Extensions({FakeExtension1.class, FakeExtension2.class})
138 | public class BaseFixture {
139 |     
140 | }    
141 |     
142 |

will install both extensions FakeExtension1, FakeExtension2.

143 |
144 | 145 | 146 | 147 |
148 |

Example - All Extensions from class hierarchy are loaded

149 |

Executing the following fixture:

150 |
151 | import org.junit.runner.RunWith;
152 | import org.concordion.integration.junit4.ConcordionRunner;
153 | import org.concordion.api.extension.Extensions;
154 | import org.concordion.api.extension.ConcordionExtension;
155 | import test.concordion.extension.fake.*;
156 |  
157 | @RunWith(ConcordionRunner.class)
158 | @Extensions({FakeExtension2.class})
159 | public class ExampleFixture extends BaseFixture {
160 | 
161 | }    
162 |     
163 | 164 |

which has superclass

165 |
166 | import org.junit.runner.RunWith;
167 | import org.concordion.integration.junit4.ConcordionRunner;
168 | import org.concordion.api.extension.Extensions;
169 | import org.concordion.api.extension.ConcordionExtension;
170 | import test.concordion.extension.fake.*;
171 |  
172 | @RunWith(ConcordionRunner.class) 
173 | @Extensions({FakeExtension1.class})
174 | public class BaseFixture {
175 |     
176 | }    
177 |     
178 |

will install both extensions FakeExtension1, FakeExtension2.

179 |
180 | 181 | 182 | 183 |

System Property

184 |

Alternatively, extensions can be specified by setting a system property. This can be useful if the extensions need to be configured independently from the fixtures.

185 |

Set the system property concordion.extensions to a comma-separated list containing:

186 | 190 |

191 | All extensions and/or extension factories must be present on the classpath. 192 | Extensions must implement org.concordion.api.extension.ConcordionExtension. 193 | Extension factories must implement org.concordion.api.extension.ConcordionExtensionFactory. 194 |

195 | 196 |

Examples

197 | 198 | 199 |
200 |

Example - System Property

201 |

Given the system property concordion.extensions is set to "test.concordion.extension.fake.FakeExtension1, test.concordion.extension.fake.FakeExtension2Factory",

202 |

Concordion fixtures will be run with both extensions FakeExtension1, FakeExtension2FromFactory.

203 |
204 | 205 | 206 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/ExtensionConfigurationTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | 4 | 5 | class ExtensionConfigurationTest(ConcordionTestCase): 6 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/ExtensionTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import unittest 3 | from base import ConcordionTestCase 4 | 5 | 6 | class ExtensionTest(ConcordionTestCase): 7 | @unittest.expectedFailure 8 | def runTest(self): 9 | super(ExtensionTest, self).runTest() -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/JavaScriptExtension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

JavaScript Extension

6 | 7 |

8 | JavaScript resources can be added to the Concordion output using an extension. 9 | The JavaScript can either be: 10 |

    11 |
  1. embedded in the Concordion output HTML, or
  2. 12 |
  3. linked, in which case the JavaScript file will be copied to the specified location in the Concordion output folder and a JavaScript declaration added to the output HTML.
  4. 13 |
14 |

15 | 16 |
17 |

Example - embedded JavaScript

18 | 19 |

An extension with embedded JavaScript is installed.

20 |

When Concordion is run, 21 | the JavaScript is embedded in the <head> section of the output HTML.

22 |
23 | 24 |
25 |

Example - linked JavaScript

26 | 27 |

An extension with linked JavaScript is installed for the JavaScript file my.js with the target location /js/my.js.

28 |

When Concordion is run, 29 | the resource /js/my.js is available in the Concordion output directory, 30 | and a link to the external stylesheet js/my.js is declared in the output HTML.

31 |
32 | 33 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/JavaScriptExtensionTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | 4 | 5 | class JavaScriptExtensionTest(ConcordionTestCase): 6 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/ResourceExtension.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Resource Extension

6 | 7 |

8 | Resources can be added to the Concordion output folder using an extension. 9 |

10 | 11 |

Static Resources

12 |

13 | "Static" resources can be created in the Concordion output folder. These resources must be available prior to running the Concordion specification. 14 |

15 | 16 |
17 | 18 |

Example

19 | 20 |

An extension is installed that copies test/concordion/o.png from the classpath to /images/o.png.

21 |

When Concordion is run, 22 | the resource /images/o.png is available in the Concordion output directory.

23 |
24 | 25 |

Dynamic Resources

26 |

27 | "Dynamic" resources can be written to the Concordion output folder during a Concordion run. These resources are typically created while running the Concordion specification, 28 | for example screenshots being generated by an assertEqualsListener. 29 |

30 |
31 |

Example

32 | 33 |

An extension is installed that creates the dynamic resource /resource/my.txt in the target.

34 |

When Concordion is run, 35 | the resource /resource/my.txt is available in the Concordion output directory.

36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/ResourceExtensionTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | 4 | 5 | class ResourceExtensionTest(ConcordionTestCase): 6 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/extension/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/listener/ExecuteTableListener.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Listeners for Executing Tables

6 | 7 |

8 | When executing tables, Concordion notifies listeners of execute events for each row of the table. 9 | Execute listeners are notified that execution is complete prior to the assertions being evaluated. 10 |

11 | 12 | 13 |
14 | 15 |

Example

16 | 17 |

An extension is installed that logs assertEquals and execute commands. When the following instrumentation:

18 |
19 | <table concordion:execute="#username = generateUsername(#fullName)">
20 |     <tr>
21 |         <th concordion:set="#fullName">Full Name</th>
22 |         <th concordion:assertEquals="#username">Username</th>
23 |     </tr>
24 |     <tr>
25 |         <td>Fred Bloggs</td>
26 |         <td>fredbloggs</td>
27 |     </tr>
28 |     <tr>
29 |         <td>John Doe</td>
30 |         <td>johndoe</td>
31 |     </tr>
32 |     <tr>
33 |         <td>Winston Churchill</td>
34 |         <td>winston</td>
35 |     </tr>
36 | </table>
37 | 
38 | 39 |

is run with a method generateUsername() that returns the 40 | full name in lowercase with spaces removed, the logged events are: 41 |

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
Event
Execute 'Fred Bloggs, fredbloggs'
Success 'fredbloggs'
Execute 'John Doe, johndoe'
Success 'johndoe'
Execute 'Winston Churchill, winston'
Failure expected:'winston' actual:'winstonchurchill'
51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/listener/Listener.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Listeners

6 | 7 |

8 | The execution of Concordion commands can be observed using listeners. Listeners have access to the HTML element with which the command is associated, 9 | and can modify the output HTML. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |

An extension is installed that listens to assertEquals, assertTrue, assertFalse and execute commands. When the following instrumentation:

17 |
18 | <p concordion:execute="#result=sqrt(#num)">The square root of <span concordion:set="#num">4.0</span> is <span concordion:assertEquals="#result">2.0</span></p>
19 | 
20 | <p concordion:execute="#result=sqrt(#num)">The square root of <span concordion:set="#num">16.0</span> is <span concordion:assertEquals="#result">3.0</span></p>
21 | 
22 | <p><span concordion:set="#num">3</span>
23 | is <span concordion:assertTrue="isPositive(#num)">is positive</span>
24 | </p>
25 | 
26 | <p><span concordion:set="#num">-4</span>
27 | is <span concordion:assertTrue="isPositive(#num)">is positive</span>
28 | </p>
29 | 
30 | <p><span concordion:set="#num">-5</span>
31 | is <span concordion:assertFalse="isPositive(#num)">is not positive</span>
32 | </p>
33 | 
34 | <p><span concordion:set="#num">6</span>
35 | is <span concordion:assertFalse="isPositive(#num)">is not positive</span>
36 | </p>
37 | 
38 | 
39 | 40 |

is run with a fixture that performs the arithmetical operations, the logged events are: 41 |

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
Event
Execute 'The square root of 4.0 is 2.0'
Success '2.0'
Execute 'The square root of 16.0 is 3.0'
Failure expected:'3.0' actual:'4.0'
Success 'is positive'
Failure expected:'is positive' actual:'== false'
Success 'is not positive'
Failure expected:'is not positive' actual:'== true'
53 | 54 |
55 | 56 |

Other listeners

57 | 58 |

Command Listeners

59 | 65 | 66 |

Processing Listeners

67 | 72 | 73 |

Further Details

74 | 75 | 78 | 79 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/listener/VerifyRowsListener.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Listeners for Verify Rows

6 | 7 |

8 | When verifying rows, Concordion notifies listeners when the expression has been evaluated and also of each missing row or surplus row in the collection returned by the expression. 9 |

10 | 11 | 12 |
13 | 14 |

Example

15 | 16 |

An extension is installed that logs assertEquals and verifyRows commands. When the following instrumentation:

17 |
18 |     <table concordion:verifyRows="#beatle : getGeorgeAndRingo()">
19 |         <tr><th concordion:assertEquals="#beatle">Matching Beatle</th></tr>
20 |         <tr><td>George Harrison</td></tr>
21 |         <tr><td>Dingo Starr</td></tr>
22 |         <tr><td>George Michael</td></tr>
23 |     </table>        
24 | 
25 | 26 |

is run with a method getGeorgeAndRingo() that returns George Harrison and Ringo Starr, the logged events are:

27 | 28 | 29 | 30 | 31 | 32 | 33 |
Event
Evaluated '#beatle : getGeorgeAndRingo()'
Success 'George Harrison'
Failure expected:'Dingo Starr' actual:'Ringo Starr'
Missing Row 'George Michael'
34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/extension/listener/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/extension/listener/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/Results.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Displaying Results

6 | 7 |

8 | After processing a document, Concordion outputs HTML based on the 9 | input document with the results marked up. 10 |

11 | 12 |

Commands

13 | 14 |

15 | The assertEquals command reports 16 | successes and 17 | failures. 18 |

19 | 20 |

21 | The assertTrue command reports 22 | successes and 23 | failures. 24 |

25 | 26 |

27 | The verifyRows command reports 28 | missing and 29 | surplus rows. 30 |

31 | 32 |

Other

33 | 34 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/ResultsTest.py: -------------------------------------------------------------------------------- 1 | from base import ConcordionTestCase 2 | 3 | 4 | class ResultsTest(ConcordionTestCase): 5 | pass -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/results/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/results/assertEquals/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/Anchors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Anchor Elements

6 | 7 |

8 | Anchor elements (<a>) have to be treated slightly differently to 9 | other elements to prevent the 'actual' value becoming a link. Rather than 10 | nesting <span> elements inside the <a>, we nest the <a> 11 | inside a new 'failure' <span>. 12 |

13 | 14 |
15 | 16 |

Example:

17 | 18 |
<a href="abc.html">ABC</a>
19 | 20 |

When marked as a failure, with actual value XYZ, it becomes:

21 | 22 |
23 | <span class="failure"> _
24 | <span class="expected"> _
25 | <a href="abc.html">ABC</a> _
26 | </span> _
27 | <span class="actual">XYZ</span> _
28 | </div>
29 | 
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/AnchorsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | 5 | 6 | # TODO 7 | class AnchorsTest(ConcordionTestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/Empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Empty Elements

6 | 7 |

8 | If the element is empty, then a non-breaking space (&#160) 9 | is inserted (so that the failure shows up when displayed in a browser). 10 |

11 | 12 |
13 | 14 |

Example: Empty Expected

15 | 16 |
<div concordion:assertEquals="acronym" />
17 | 18 |

19 | When marked as a failure, with actual value 20 | XYZ, it becomes: 21 |

22 | 23 |
24 | <div concordion:assertEquals="acronym" class="failure"> _
25 | <del class="expected">&#160;</del>
26 | <ins class="actual">XYZ</ins> _
27 | </div>
28 | 
29 |
30 | 31 |
32 | 33 |

Example: Empty Actual

34 | 35 |
<div concordion:assertEquals="acronym">ABC</div>
36 | 37 |

When marked as a failure, with a blank actual value, it becomes:

38 | 39 |
40 | <div concordion:assertEquals="acronym" class="failure"> _
41 | <del class="expected">ABC</del>
42 | <ins class="actual">&#160;</ins> _
43 | </div>
44 | 
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/EmptyTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from spec.concordion.results.assertEquals.failure.FailureTest import FailureTest 4 | 5 | 6 | class EmptyTest(FailureTest): 7 | pass 8 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/Failure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Failures 5 | 6 | 7 | 8 |

Failures

9 | 10 |

11 | Failures are indicated by adding a class="failure" attribute to the element, 12 | and replacing the contents with a <del> and <ins> 13 | elements containing the expected value and the actual value respectively. 14 |

15 | 16 |
17 | 18 |

Example

19 | 20 |
<p concordion:assertEquals="acronym">ABC</p>
21 | 22 |

When marked as a failure, with acroynm 23 | returning XYZ, it becomes:

24 | 25 |
26 | <p concordion:assertEquals="acronym" class="failure"> _
27 | <del class="expected">ABC</del>
28 | <ins class="actual">XYZ</ins> _
29 | </p>
30 | 
31 |

Note: The underscores indicate line continuations for readability only. They are not output. In reality, it is all in one long line.

32 | 33 |
34 | 35 |

Further Details

36 | 37 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/FailureTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | from test_rig import TestRig 5 | 6 | 7 | class FailureTest(ConcordionTestCase): 8 | def setUp(self): 9 | self.acronym = None 10 | 11 | def renderAsFailure(self, fragment, acronym): 12 | self.acronym = acronym 13 | test_rig = TestRig(fixture=self) 14 | test_rig.process_fragment(fragment) 15 | return test_rig.get_output_fragment_xml() 16 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/NestedElements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Nested Elements

6 | 7 |

8 | Nested elements are moved into the 'expected' span. 9 |

10 | 11 |
12 | 13 |

Example

14 | 15 |
<div concordion:assertEquals="acronym">My <em>simple</em> example</div>
16 | 17 |

When marked as a failure, with actual value XYZ, it becomes:

18 | 19 |
20 | <div concordion:assertEquals="acronym" class="failure"> _
21 | <del class="expected">My <em>simple</em> example</del>
22 | <ins class="actual">XYZ</ins> _
23 | </div>
24 | 
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/NestedElementsTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from spec.concordion.results.assertEquals.failure.FailureTest import FailureTest 4 | 5 | 6 | class NestedElementsTest(FailureTest): 7 | pass 8 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/failure/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/results/assertEquals/failure/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/Empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Empty Elements

6 | 7 |

8 | If the element is empty then a non-breaking space (&#160) is inserted so 9 | that there is something there to show the success. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |
<span concordion:assertEquals="username"/>
17 | 18 |

When marked as a success becomes:

19 | 20 |
<span concordion:assertEquals="username" class="success">&#160;</span>
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/EmptyTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from spec.concordion.results.assertEquals.success.SuccessTest import SuccessTest 4 | 5 | 6 | class EmptyTest(SuccessTest): 7 | username = "" 8 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/HasAttributes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Element Already Has Attributes

6 | 7 |

8 | If an element already has attributes then these attributes will be 9 | retained. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |
<span id="example" concordion:assertEquals="username">fred</span>
17 | 18 |

When marked as a success becomes:

19 | 20 |
<span id="example" concordion:assertEquals="username" class="success">fred</span>
21 |
22 | 23 |

Further Details

24 | 25 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/HasAttributesTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from spec.concordion.results.assertEquals.success.SuccessTest import SuccessTest 4 | 5 | 6 | class HasAttributesTest(SuccessTest): 7 | pass 8 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/HasClassAttribute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Element Already Has A Class Attribute

6 | 7 |

8 | If an element already has a class attribute then the new attribute will 9 | be appended to the existing value, separated by space. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |
<span concordion:assertEquals="username" class="blah">fred</span>
17 | 18 |

When marked as a success becomes:

19 | 20 |
<span concordion:assertEquals="username" class="blah success">fred</span>
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/HasClassAttributeTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from spec.concordion.results.assertEquals.success.SuccessTest import SuccessTest 4 | 5 | 6 | class HasClassAttributeTest(SuccessTest): 7 | pass 8 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/Success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Success

6 | 7 |

8 | Successes are indicated by adding a class="success" attribute to the element. 9 | Typically this will result in the element being displayed in green (depending on the CSS stylesheet). 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |
17 | <span concordion:assertEquals="username">fred</span>
18 | 
19 | 20 |

When marked as a success becomes:

21 | 22 |
23 | <span concordion:assertEquals="username" class="success">fred</span>
24 | 
25 |
26 | 27 |

Further Details

28 | 29 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/SuccessTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | from test_rig import TestRig 5 | 6 | 7 | class SuccessTest(ConcordionTestCase): 8 | username = "fred" 9 | 10 | def renderAsSuccess(self, fragment): 11 | test_rig = TestRig(fixture=self) 12 | test_rig.process_fragment(fragment) 13 | return test_rig.get_output_fragment_xml() 14 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertEquals/success/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/results/assertEquals/success/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertTrue/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/results/assertTrue/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertTrue/failure/Failure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Failures 5 | 6 | 7 | 8 |

Failures

9 | 10 |

11 | Failures are indicated by adding a class="failure" attribute to the element, 12 | and replacing the contents with a <del> and <ins> 13 | elements containing the expected value and the actual value respectively. 14 |

15 | 16 |
17 | 18 |

Example

19 | 20 |

21 | The output of running this: 22 |

23 | 24 |
<p concordion:assertTrue="isPalindrome(#TEXT)">ABB</p>
25 | 26 |

Looks like this:

27 | 28 |
29 | <p concordion:assertTrue="isPalindrome(#TEXT)" class="failure"> _
30 | <del class="expected">ABB</del>
31 | <ins class="actual">== false</ins> _
32 | </p>
33 | 
34 |

Note: The underscores indicate line continuations for readability only. They are not output. In reality, it is all in one long line.

35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertTrue/failure/FailureTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class FailureTest(ConcordionTestCase): 7 | def isPalindrome(self, s): 8 | return s == s[::-1] 9 | 10 | def render(self, fragment): 11 | rig = TestRig(fixture=self) 12 | rig.process_fragment(fragment) 13 | return rig.get_output_fragment_xml() 14 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertTrue/failure/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/results/assertTrue/failure/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertTrue/success/Success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Success 5 | 6 | 7 | 8 |

Success

9 | 10 |

11 | Successes are indicated by adding a class="success" attribute to the element. 12 |

13 | 14 |
15 | 16 |

Example

17 | 18 |

19 | The output of running this: 20 |

21 | 22 |
<p concordion:assertTrue="isPalindrome(#TEXT)">ABBA</p>
23 | 24 |

Looks like this:

25 | 26 |
27 | <p concordion:assertTrue="isPalindrome(#TEXT)" class="success">ABBA</p>
28 | 
29 | 30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertTrue/success/SuccessTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from base import ConcordionTestCase 3 | from test_rig import TestRig 4 | 5 | 6 | class SuccessTest(ConcordionTestCase): 7 | def isPalindrome(self, s): 8 | return s == s[::-1] 9 | 10 | def render(self, fragment): 11 | rig = TestRig(fixture=self) 12 | rig.process_fragment(fragment) 13 | return rig.get_output_fragment_xml() 14 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/concordion/results/assertTrue/success/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/concordion/results/assertTrue/success/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/examples/Demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Demo

6 | 7 |

8 | After a user logs into the system, a greeting is 9 | displayed saying "Hello [user's first name]!" 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |

17 | When user Bob 18 | logs in, the greeting will be: 19 | Hello Bob! 20 |

21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/examples/DemoTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | 5 | 6 | class DemoTest(ConcordionTestCase): 7 | def greetingFor(self, firstName): 8 | return "Hello %s!" % firstName 9 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/examples/PartialMatches.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Partial Matches

6 | 7 |

8 | Username searches return partial matches, i.e. all usernames containing 9 | the search string are returned. 10 |

11 | 12 |
13 | 14 |

Example

15 | 16 |

Given these users:

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Username
john.lennon
ringo.starr
george.harrison
paul.mcartney
35 | 36 |

Searching for "arr" will return:

37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
Matching Usernames
george.harrison
ringo.starr
49 | 50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/examples/PartialMatchesTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from base import ConcordionTestCase 4 | 5 | 6 | class PartialMatchesTest(ConcordionTestCase): 7 | def setUp(self): 8 | self.usernames = [] 9 | 10 | def setUpUser(self, username): 11 | self.usernames.append(username) 12 | 13 | def getSearchResultsFor(self, searchString): 14 | results = [] 15 | for username in self.usernames: 16 | if searchString in username: 17 | results.append(username) 18 | return sorted(results) 19 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/examples/Spike.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spike 6 | 7 | 8 |

Spike

9 | 10 |

11 | The greeting Hello David! 12 | should be displayed for David 13 | when he logs in. 14 |

15 | 16 |

Doing something

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
First NameLast Name
JohnTravolta
FrankZappa
32 | 33 | 34 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/examples/SpikeTest.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from collections import namedtuple 3 | 4 | from base import ConcordionTestCase 5 | 6 | Person = namedtuple("Person", ["firstName", "lastName"]) 7 | 8 | 9 | class SpikeTest(ConcordionTestCase): 10 | def getGreetingFor(self, name): 11 | return "Hello %s!" % name 12 | 13 | def doSomething(self, text): 14 | pass 15 | 16 | def getPeople(self): 17 | return [Person("John", "Travolta"), Person("Frank", "Zappa")] 18 | -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/spec/examples/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/spec/run_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | sys.path.insert(0, os.path.dirname(__file__)) 6 | sys.path.insert(1, os.path.join(os.path.dirname(__file__), "..")) 7 | sys.path.insert(3, os.path.join(os.path.dirname(__file__), "..", "..")) 8 | 9 | import IndexTest 10 | 11 | 12 | def test_run(): 13 | unittest.TextTestRunner().run(IndexTest.IndexTest()) 14 | -------------------------------------------------------------------------------- /pyconcordion2/tests/test_rig.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | from io import StringIO 3 | import re 4 | 5 | from lxml import etree 6 | from mock import Mock, patch 7 | 8 | from commands import Commander 9 | 10 | 11 | def text_to_bool(text): 12 | if text.lower() in ("y", "true", "1"): 13 | return True 14 | return False 15 | 16 | 17 | class TestRig(object): 18 | def __init__(self, fixture=None): 19 | self.fixture = fixture or self 20 | self.patcher = None 21 | 22 | def stub_result(self, result): 23 | if isinstance(result, Exception): 24 | self.patcher = patch('expression_parser.execute_within_context', new=Mock(side_effect=result)) 25 | else: 26 | self.patcher = patch('expression_parser.execute_within_context', new=Mock(return_value=result)) 27 | self.patcher.start() 28 | 29 | def process_html(self, html): 30 | commander = Commander(self.fixture, StringIO(html)) 31 | commander.process() 32 | self.result = commander.result 33 | if self.patcher: 34 | self.patcher.stop() 35 | return self.result 36 | 37 | def process_fragment(self, fragment): 38 | html = """ 39 | 40 | 41 | {} 42 | 43 | """.format(fragment) 44 | return self.process_html(html) 45 | 46 | def get_output_fragment_xml(self): 47 | regex = re.compile("") 48 | return re.sub(regex, "", etree.tostring(self.result.root_element.xpath("//fragment")[0])).strip() 49 | 50 | def success_or_failure(self): 51 | is_failure = self.result.num_exception or self.result.num_failure 52 | return "FAILURE" if is_failure else "SUCCESS" 53 | -------------------------------------------------------------------------------- /pyconcordion2/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/concordion/pyconcordion2/49287e72687e0c14c459bdee56e0b62ba6b003c0/pyconcordion2/tests/unit/__init__.py -------------------------------------------------------------------------------- /pyconcordion2/tests/unit/expression_parser_test.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import unittest 3 | 4 | from pyconcordion2 import expression_parser 5 | 6 | 7 | class ExpressionParserTest(unittest.TestCase): 8 | def test_variable_only(self): 9 | expression_str = "test" 10 | expression_tree = expression_parser.parse(expression_str) 11 | self.assertEqual(expression_tree.variable_name, expression_str) 12 | 13 | def test_function_only_with_no_parameters(self): 14 | expression_str = "get_some_thing()" 15 | expression_tree = expression_parser.parse(expression_str) 16 | self.assertEqual("get_some_thing", expression_tree.function_name) 17 | self.assertItemsEqual([], expression_tree.parameters) 18 | 19 | def test_function_only_with_one_parameter(self): 20 | expression_str = "get_some_thing(param1)" 21 | expression_tree = expression_parser.parse(expression_str) 22 | self.assertEqual("get_some_thing", expression_tree.function_name) 23 | self.assertItemsEqual(["param1"], expression_tree.parameters) 24 | 25 | def test_function_only_with_two_parameters(self): 26 | expression_str = "get_some_thing(param1, param2)" 27 | expression_tree = expression_parser.parse(expression_str) 28 | self.assertEqual("get_some_thing", expression_tree.function_name) 29 | self.assertItemsEqual(["param1", "param2"], expression_tree.parameters) 30 | 31 | def test_function_only_with_two_parameters_assign_to_variable(self): 32 | expression_str = "result = get_some_thing(param1, param2)" 33 | expression_tree = expression_parser.parse(expression_str) 34 | self.assertEqual("get_some_thing", expression_tree.function_name) 35 | self.assertItemsEqual(["param1", "param2"], expression_tree.parameters) 36 | self.assertEqual("result", expression_tree.variable_name) 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | enum34 2 | mock 3 | lxml 4 | pyparsing 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | # for pip >= 10 5 | try: 6 | from pip._internal.req import parse_requirements 7 | except ImportError: # for pip <= 9.0.3 8 | from pip.req import parse_requirements 9 | 10 | current_dir = os.path.dirname(os.path.realpath(__file__)) 11 | requirements_file = 'requirements.txt' 12 | requirements_file_path = os.path.join(current_dir, requirements_file) 13 | 14 | install_reqs = parse_requirements(requirements_file_path,session=False) 15 | reqs = [str(ir.req) for ir in install_reqs] 16 | 17 | setup(name='pyconcordion2', 18 | version='0.15.1', 19 | description="Concordion Python Port", 20 | long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst'), 'rU').read(), 21 | # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers 22 | classifiers=["Topic :: Software Development :: Testing", 23 | "License :: OSI Approved :: Apache Software License"], 24 | keywords='concordion acceptance-test', 25 | author='John Jiang', 26 | author_email='johnjiang101@gmail.com', 27 | url='https://github.com/concordion/pyconcordion2', 28 | license='Apache Software License', 29 | packages=find_packages(), 30 | package_data={ 31 | 'pyconcordion2': [ 32 | 'resources/*.css', 33 | 'resources/*.js', 34 | ], 35 | }, 36 | include_package_data=True, 37 | zip_safe=False, 38 | install_requires=reqs, 39 | tests_require=['mock'], 40 | # test_suite='runtests.get_suite', 41 | entry_points=""" 42 | # -*- Entry points: -*- 43 | """, 44 | ) 45 | --------------------------------------------------------------------------------