From 7c719c5624963741340e5a58522c9ea6a5a89f8f Mon Sep 17 00:00:00 2001 From: Colm O'Connor Date: Sat, 1 Aug 2015 15:09:25 +0800 Subject: [PATCH] Added hitch testing framework and stub test. --- {{cookiecutter.repo_name}}/tests/base.yml | 4 + {{cookiecutter.repo_name}}/tests/engine.py | 164 ++++++++++++++++++ .../tests/hitchreqs.txt | 26 +++ {{cookiecutter.repo_name}}/tests/settings.yml | 33 ++++ {{cookiecutter.repo_name}}/tests/stub.test | 10 ++ 5 files changed, 237 insertions(+) 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}}/tests/base.yml b/{{cookiecutter.repo_name}}/tests/base.yml new file mode 100644 index 000000000..a1d5e125a --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/base.yml @@ -0,0 +1,4 @@ +{% for python_version in python_versions %} +{% block test scoped %} +{% endblock %} +{% endfor %} diff --git a/{{cookiecutter.repo_name}}/tests/engine.py b/{{cookiecutter.repo_name}}/tests/engine.py new file mode 100644 index 000000000..4c711b22b --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/engine.py @@ -0,0 +1,164 @@ +from hitchserve import ServiceBundle +from os import path, system, chdir +from subprocess import call, PIPE +import hitchpostgres +import hitchselenium +import hitchpython +import hitchredis +import hitchtest +import hitchsmtp +import hitchcron +import IPython +import sys + +# 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() + + {% if cookiecutter.celery_support == "y" %} + redis_package = hitchredis.RedisPackage(version="2.8.4") + redis_package.build() + redis_package.verify() + {% endif %} + + self.services = ServiceBundle( + project_directory=PROJECT_DIRECTORY, + startup_timeout=float(self.settings["startup_timeout"]), + shutdown_timeout=5.0, + ) + + postgres_user = hitchpostgres.PostgresUser("trackfeatures", "password") + + self.services['Postgres'] = hitchpostgres.PostgresService( + postgres_package=postgres_package, + users=[postgres_user, ], + databases=[hitchpostgres.PostgresDatabase("trackfeatures", 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'], + ) + + {% if cookiecutter.celery_support == "y" %} + self.services['Redis'] = hitchredis.RedisService( + redis_package=redis_package, + port=16379, + ) + + self.services['Celery'] = hitchpython.CeleryService( + python=python_package.python, + version="3.1.18", + app="trackfeatures.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) + ) + + #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=15, + 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 call(["which", "kaching"], stdout=PIPE, stderr=PIPE) == 0: + call(["kaching", "fail"]) # sudo pip install kaching for sad sound + if self.settings.get("pause_on_failure", False): + self.pause(message=self.stacktrace.to_template()) + + def on_success(self): + """Ka-ching!""" + if not self.settings['quiet'] and call(["which", "kaching"], stdout=PIPE, stderr=PIPE) == 0: + call(["kaching", "pass"]) # sudo pip install kaching for happy sound + if self.settings.get("pause_on_success", False): + self.pause(message="SUCCESS") + + def tear_down(self): + """Commit genocide on the 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 000000000..68c5edcc7 --- /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 000000000..dd004a6ba --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/settings.yml @@ -0,0 +1,33 @@ +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://trackfeatures:password@127.0.0.1:15432/trackfeatures + SECRET_KEY: cj5^uos4tfCdfghjku5hq$9$(@-7-^e9&x_3vyfkigvspm6c=+ + 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: + - firefox + - automake + - libtool diff --git a/{{cookiecutter.repo_name}}/tests/stub.test b/{{cookiecutter.repo_name}}/tests/stub.test new file mode 100644 index 000000000..d6bf6af59 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/stub.test @@ -0,0 +1,10 @@ +{% extends "base.yml" %} +{% block test %} +- engine: engine.py:ExecutionEngine + name: Stub {{ python_version }} + preconditions: + python_version: "{{ python_version }}" + scenario: + - Load website + - Pause +{% endblock %}