From 18b1843481360042597a5e2126c23a5d0824b7c9 Mon Sep 17 00:00:00 2001 From: Colm O'Connor Date: Sat, 1 Aug 2015 18:26:06 +0800 Subject: [PATCH 1/4] Added support for the Hitch integration testing framework. --- {{cookiecutter.repo_name}}/README.rst | 20 ++- {{cookiecutter.repo_name}}/requirements.apt | 20 +++ {{cookiecutter.repo_name}}/tests/base.yml | 4 + {{cookiecutter.repo_name}}/tests/engine.py | 157 ++++++++++++++++++ .../tests/hitchreqs.txt | 26 +++ {{cookiecutter.repo_name}}/tests/settings.yml | 59 +++++++ {{cookiecutter.repo_name}}/tests/stub.test | 10 ++ 7 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 {{cookiecutter.repo_name}}/tests/base.yml create mode 100644 {{cookiecutter.repo_name}}/tests/engine.py create mode 100644 {{cookiecutter.repo_name}}/tests/hitchreqs.txt create mode 100644 {{cookiecutter.repo_name}}/tests/settings.yml create mode 100644 {{cookiecutter.repo_name}}/tests/stub.test diff --git a/{{cookiecutter.repo_name}}/README.rst b/{{cookiecutter.repo_name}}/README.rst index 786232f5..ecb5f42b 100644 --- a/{{cookiecutter.repo_name}}/README.rst +++ b/{{cookiecutter.repo_name}}/README.rst @@ -152,8 +152,26 @@ The email server listens on 127.0.0.1:1025 It's time to write the code!!! +Running end to end integration tests +------------------------------------ + +To run the tests, enter the {{cookiecutter.repo_name}}/tests directory and run the following commands:: + + $ hitch init + +Then run the stub test:: + + $ hitch test stub.test + +This will download and compile python, postgres and redis and install all python requirements so the first time it runs it may take a while. + +Subsequent test runs will be much quicker. + +The testing framework runs Django, Celery (if enabled), Postgres, HitchSMTP (a mock SMTP server), Firefox/Selenium and Redis. + + Deployment ------------- +---------- It is possible to deploy to Heroku or to your own server by using Dokku, an open source Heroku clone. diff --git a/{{cookiecutter.repo_name}}/requirements.apt b/{{cookiecutter.repo_name}}/requirements.apt index 46cfaac2..bbbaa3c9 100644 --- a/{{cookiecutter.repo_name}}/requirements.apt +++ b/{{cookiecutter.repo_name}}/requirements.apt @@ -22,3 +22,23 @@ libwebp-dev ##django-extensions graphviz-dev + +##hitch +python-setuptools +python3-dev +python-virtualenv +python-pip +firefox +automake +libtool +libreadline6 +libreadline6-dev +libreadline-dev +libsqlite3-dev +libxml2 +libxml2-dev +libssl-dev +libbz2-dev +wget +curl +llvm diff --git a/{{cookiecutter.repo_name}}/tests/base.yml b/{{cookiecutter.repo_name}}/tests/base.yml new file mode 100644 index 00000000..844f1271 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/base.yml @@ -0,0 +1,4 @@ +{% raw %}{% for python_version in python_versions %} +{% block test scoped %} +{% endblock %} +{% endfor %}{% endraw %} diff --git a/{{cookiecutter.repo_name}}/tests/engine.py b/{{cookiecutter.repo_name}}/tests/engine.py new file mode 100644 index 00000000..ab4ba697 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/engine.py @@ -0,0 +1,157 @@ +from subprocess import call +from os import path +import hitchpostgres +import hitchselenium +import hitchpython +import hitchserve +import hitchredis +import hitchtest +import hitchsmtp + + +# Get directory above this file +PROJECT_DIRECTORY = path.abspath(path.join(path.dirname(__file__), '..')) + + +class ExecutionEngine(hitchtest.ExecutionEngine): + """Engine for orchestating and interacting with the app.""" + + def set_up(self): + """Ensure virtualenv present, then run all services.""" + python_package = hitchpython.PythonPackage( + python_version=self.preconditions['python_version'] + ) + python_package.build() + python_package.verify() + + call([ + python_package.pip, "install", "-r", + path.join(PROJECT_DIRECTORY, "requirements/local.txt") + ]) + + postgres_package = hitchpostgres.PostgresPackage( + version=self.settings["postgres_version"], + ) + postgres_package.build() + postgres_package.verify() + + redis_package = hitchredis.RedisPackage(version="2.8.4") + redis_package.build() + redis_package.verify() + + self.services = hitchserve.ServiceBundle( + project_directory=PROJECT_DIRECTORY, + startup_timeout=float(self.settings["startup_timeout"]), + shutdown_timeout=5.0, + ) + + postgres_user = hitchpostgres.PostgresUser("{{cookiecutter.repo_name}}", "password") + + self.services['Postgres'] = hitchpostgres.PostgresService( + postgres_package=postgres_package, + users=[postgres_user, ], + databases=[hitchpostgres.PostgresDatabase("{{cookiecutter.repo_name}}", postgres_user), ] + ) + + self.services['HitchSMTP'] = hitchsmtp.HitchSMTPService(port=1025) + + self.services['Django'] = hitchpython.DjangoService( + python=python_package.python, + port=8000, + version=str(self.settings.get("django_version")), + settings="config.settings.local", + needs=[self.services['Postgres'], ], + env_vars=self.settings['environment_variables'], + ) + + self.services['Redis'] = hitchredis.RedisService( + redis_package=redis_package, + port=16379, + ) +{% if cookiecutter.celery_support == "y" %} + self.services['Celery'] = hitchpython.CeleryService( + python=python_package.python, + version="3.1.18", + app="{{cookiecutter.repo_name}}.taskapp", loglevel="INFO", + needs=[ + self.services['Redis'], self.services['Django'], + ], + env_vars=self.settings['environment_variables'], + ) +{% endif %} + self.services['Firefox'] = hitchselenium.SeleniumService( + xvfb=self.settings.get("quiet", False) + ) + +# import hitchcron +# self.services['Cron'] = hitchcron.CronService( +# run=self.services['Django'].manage("trigger").command, +# every=1, +# needs=[ self.services['Django'], ], +# ) + + self.services.startup(interactive=False) + + # Configure selenium driver + self.driver = self.services['Firefox'].driver + self.driver.set_window_size(self.settings['window_size']['height'], self.settings['window_size']['width']) + self.driver.set_window_position(0, 0) + self.driver.implicitly_wait(2.0) + self.driver.accept_next_alert = True + + def pause(self, message=None): + """Stop. IPython time.""" + if hasattr(self, 'services'): + self.services.start_interactive_mode() + self.ipython(message) + if hasattr(self, 'services'): + self.services.stop_interactive_mode() + + def load_website(self): + """Navigate to website in Firefox.""" + self.driver.get(self.services['Django'].url()) + + def click(self, on): + """Click on HTML id.""" + self.driver.find_element_by_id(on).click() + + def fill_form(self, **kwargs): + """Fill in a form with id=value.""" + for element, text in kwargs.items(): + self.driver.find_element_by_id(element).send_keys(text) + + def click_submit(self): + """Click on a submit button if it exists.""" + self.driver.find_element_by_css_selector("button[type=\"submit\"]").click() + + def confirm_emails_sent(self, number): + """Count number of emails sent by app.""" + assert len(self.services['HitchSMTP'].logs.json()) == int(number) + + def wait_for_email(self, containing=None): + """Wait for, and return email.""" + self.services['HitchSMTP'].logs.out.tail.until_json( + lambda email: containing in email['payload'] or containing in email['subject'], + timeout=25, + lines_back=1, + ) + + def time_travel(self, days=""): + """Make all services think that time has skipped forward.""" + self.services.time_travel(days=int(days)) + + def on_failure(self): + """Stop and IPython.""" + if not self.settings['quiet']: + if self.settings.get("pause_on_failure", False): + self.pause(message=self.stacktrace.to_template()) + + def on_success(self): + """Pause on success if enabled.""" + if self.settings.get("pause_on_success", False): + self.pause(message="SUCCESS") + + def tear_down(self): + """Shut down services required to run your test.""" + if hasattr(self, 'services'): + self.services.shutdown() diff --git a/{{cookiecutter.repo_name}}/tests/hitchreqs.txt b/{{cookiecutter.repo_name}}/tests/hitchreqs.txt new file mode 100644 index 00000000..68c5edcc --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/hitchreqs.txt @@ -0,0 +1,26 @@ +click==4.0 +colorama==0.3.3 +faketime==0.9.6.3 +hitchcron==0.2 +hitchpostgres==0.6.1 +hitchpython==0.2 +hitchredis==0.4.1 +hitchselenium==0.3.1 +hitchserve==0.4.1 +hitchsmtp==0.2.1 +hitchtest==0.6.8 +humanize==0.5.1 +ipython==3.1.0 +Jinja2==2.7.3 +MarkupSafe==0.23 +patool==1.7 +psutil==3.0.0 +python-build==0.2.1 +pyuv==1.0.2 +PyYAML==3.11 +requests==2.7.0 +selenium==2.46.0 +six==1.9.0 +tblib==1.0.1 +tornado==4.2 +xeger==0.3 diff --git a/{{cookiecutter.repo_name}}/tests/settings.yml b/{{cookiecutter.repo_name}}/tests/settings.yml new file mode 100644 index 00000000..7ea14f07 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/settings.yml @@ -0,0 +1,59 @@ +postgres_version: 9.3.9 +redis_version: 2.8.4 +django_version: 1.8.3 +celery_version: 3.1.18 +pause_on_success: false +pause_on_failure: true +startup_timeout: 45 +environment_variables: + DATABASE_URL: postgres://{{cookiecutter.repo_name}}:password@127.0.0.1:15432/{{cookiecutter.repo_name}} + SECRET_KEY: cj5^uos4tfCdfghjkf5hq$9$(@-79^e9&x$3vyf#igvsfm4d=+ + CELERY_BROKER_URL: redis://localhost:16379 +window_size: + width: 450 + height: 450 +python_versions: + - 2.7.10 +environment: + - approved_platforms: + - linux + - darwin + - freeports: + - 1025 + - 8000 + - 15432 + - 16379 + - brew: + - libtool + - automake + - node + - debs: + - python-setuptools + - python3-dev + - python-virtualenv + - python-pip + - firefox + - automake + - libtool + - libreadline6 + - libreadline6-dev + - libreadline-dev + - libsqlite3-dev + - libpq-dev + - libxml2 + - libxml2-dev + - libssl-dev + - libbz2-dev + - wget + - curl + - llvm + - graphviz-dev + - libtiff4-dev + - libjpeg8-dev + - libfreetype6-dev + - liblcms1-dev + - libwebp-dev + - zlib1g-dev + - gettext + - python-dev + - build-essential diff --git a/{{cookiecutter.repo_name}}/tests/stub.test b/{{cookiecutter.repo_name}}/tests/stub.test new file mode 100644 index 00000000..88b4a1d0 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/stub.test @@ -0,0 +1,10 @@ +{% raw %}{% extends "base.yml" %} +{% block test %} +- engine: engine.py:ExecutionEngine + name: Stub {{ python_version }} + preconditions: + python_version: "{{ python_version }}" + scenario: + - Load website + - Pause +{% endblock %}{% endraw %} From 3f2484d75cd314bcfea43bb9be8e4128b38bf56e Mon Sep 17 00:00:00 2001 From: Colm O'Connor Date: Tue, 4 Aug 2015 23:39:27 +0800 Subject: [PATCH 2/4] Enable hitch tests to be run in Travis CI. --- {{cookiecutter.repo_name}}/.travis.yml | 16 ++++++++++++++++ {{cookiecutter.repo_name}}/tests/engine.py | 3 ++- {{cookiecutter.repo_name}}/tests/hitchreqs.txt | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 {{cookiecutter.repo_name}}/.travis.yml diff --git a/{{cookiecutter.repo_name}}/.travis.yml b/{{cookiecutter.repo_name}}/.travis.yml new file mode 100644 index 00000000..f7e40a1e --- /dev/null +++ b/{{cookiecutter.repo_name}}/.travis.yml @@ -0,0 +1,16 @@ +before_install: + - sudo apt-get update -qq + - sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb + - sudo apt-get install -qq libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms1-dev libwebp-dev + - sudo apt-get install -qq graphviz-dev python-setuptools python3-dev python-virtualenv python-pip + - sudo apt-get install -qq firefox automake libtool libreadline6 libreadline6-dev libreadline-dev + - sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm +language: python +python: + - "3.4" +install: + - "pip install hitch" + - "cd tests" + - "hitch init" +script: + - "hitch test . --extra '{\"xvfb\":true, \"pause_on_failure\":false}'" diff --git a/{{cookiecutter.repo_name}}/tests/engine.py b/{{cookiecutter.repo_name}}/tests/engine.py index ab4ba697..388b08fd 100644 --- a/{{cookiecutter.repo_name}}/tests/engine.py +++ b/{{cookiecutter.repo_name}}/tests/engine.py @@ -80,7 +80,8 @@ class ExecutionEngine(hitchtest.ExecutionEngine): ) {% endif %} self.services['Firefox'] = hitchselenium.SeleniumService( - xvfb=self.settings.get("quiet", False) + xvfb=self.settings.get("quiet", False), + no_libfaketime=True, ) # import hitchcron diff --git a/{{cookiecutter.repo_name}}/tests/hitchreqs.txt b/{{cookiecutter.repo_name}}/tests/hitchreqs.txt index 68c5edcc..c14254a6 100644 --- a/{{cookiecutter.repo_name}}/tests/hitchreqs.txt +++ b/{{cookiecutter.repo_name}}/tests/hitchreqs.txt @@ -6,9 +6,9 @@ hitchpostgres==0.6.1 hitchpython==0.2 hitchredis==0.4.1 hitchselenium==0.3.1 -hitchserve==0.4.1 +hitchserve==0.4.2 hitchsmtp==0.2.1 -hitchtest==0.6.8 +hitchtest==0.7 humanize==0.5.1 ipython==3.1.0 Jinja2==2.7.3 From a8836b390b142b97c3e2edd537a388ab59e55129 Mon Sep 17 00:00:00 2001 From: Colm O'Connor Date: Tue, 4 Aug 2015 23:47:37 +0800 Subject: [PATCH 3/4] Added note to the README about hitch not running on windows, and instructions on how to install the test runner. --- {{cookiecutter.repo_name}}/README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/{{cookiecutter.repo_name}}/README.rst b/{{cookiecutter.repo_name}}/README.rst index ecb5f42b..ba9f4d1f 100644 --- a/{{cookiecutter.repo_name}}/README.rst +++ b/{{cookiecutter.repo_name}}/README.rst @@ -155,6 +155,12 @@ It's time to write the code!!! Running end to end integration tests ------------------------------------ +N.B. The integration tests will not run on Windows. + +To install the test runner:: + + $ sudo pip install hitch + To run the tests, enter the {{cookiecutter.repo_name}}/tests directory and run the following commands:: $ hitch init From 4f68d3eb2a830a9fe424b561a6ca4664a0c582d7 Mon Sep 17 00:00:00 2001 From: Colm O'Connor Date: Wed, 12 Aug 2015 08:49:06 +0800 Subject: [PATCH 4/4] Removed sudo from pip install hitch, since the test invoker strictly speaking does not need to be run globally. --- {{cookiecutter.repo_name}}/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.repo_name}}/README.rst b/{{cookiecutter.repo_name}}/README.rst index ba9f4d1f..acd8c066 100644 --- a/{{cookiecutter.repo_name}}/README.rst +++ b/{{cookiecutter.repo_name}}/README.rst @@ -159,7 +159,7 @@ N.B. The integration tests will not run on Windows. To install the test runner:: - $ sudo pip install hitch + $ pip install hitch To run the tests, enter the {{cookiecutter.repo_name}}/tests directory and run the following commands::