From aa2d94542336f359a1205520e844c1bcff53712b Mon Sep 17 00:00:00 2001 From: Richard Purdie Date: Mon, 1 Aug 2016 09:38:23 +0100 Subject: [PATCH] bitbake: lib/toaster: Fix missing new files from previous commits (Bitbake rev: f77e6f21a2cc57a3fcb5970437e55cfae39849a3) Signed-off-by: Richard Purdie --- .../0010_delete_layer_source_references.py | 118 ++++++++++ .../orm/migrations/0011_delete_layersource.py | 17 ++ .../0012_use_release_instead_of_up_branch.py | 62 +++++ .../tests/browser/selenium_helpers_base.py | 218 ++++++++++++++++++ 4 files changed, 415 insertions(+) create mode 100644 bitbake/lib/toaster/orm/migrations/0010_delete_layer_source_references.py create mode 100644 bitbake/lib/toaster/orm/migrations/0011_delete_layersource.py create mode 100644 bitbake/lib/toaster/orm/migrations/0012_use_release_instead_of_up_branch.py create mode 100644 bitbake/lib/toaster/tests/browser/selenium_helpers_base.py diff --git a/bitbake/lib/toaster/orm/migrations/0010_delete_layer_source_references.py b/bitbake/lib/toaster/orm/migrations/0010_delete_layer_source_references.py new file mode 100644 index 0000000000..f67388e990 --- /dev/null +++ b/bitbake/lib/toaster/orm/migrations/0010_delete_layer_source_references.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0009_target_package_manifest_path'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='releaselayersourcepriority', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='releaselayersourcepriority', + name='layer_source', + ), + migrations.RemoveField( + model_name='releaselayersourcepriority', + name='release', + ), + migrations.DeleteModel( + name='ImportedLayerSource', + ), + migrations.DeleteModel( + name='LayerIndexLayerSource', + ), + migrations.DeleteModel( + name='LocalLayerSource', + ), + migrations.RemoveField( + model_name='recipe', + name='layer_source', + ), + migrations.RemoveField( + model_name='recipe', + name='up_id', + ), + migrations.AlterField( + model_name='layer', + name='up_date', + field=models.DateTimeField(default=django.utils.timezone.now, null=True), + ), + migrations.AlterField( + model_name='layer_version', + name='layer_source', + field=models.IntegerField(default=0, choices=[(0, 'local'), (1, 'layerindex'), (2, 'imported'), (3, 'build')]), + ), + migrations.AlterField( + model_name='layer_version', + name='up_date', + field=models.DateTimeField(default=django.utils.timezone.now, null=True), + ), + migrations.AlterUniqueTogether( + name='branch', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='layer', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='layer_version', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='layerversiondependency', + unique_together=set([]), + ), + migrations.AlterUniqueTogether( + name='machine', + unique_together=set([]), + ), + migrations.DeleteModel( + name='ReleaseLayerSourcePriority', + ), + migrations.RemoveField( + model_name='branch', + name='layer_source', + ), + migrations.RemoveField( + model_name='branch', + name='up_id', + ), + migrations.RemoveField( + model_name='layer', + name='layer_source', + ), + migrations.RemoveField( + model_name='layer', + name='up_id', + ), + migrations.RemoveField( + model_name='layer_version', + name='up_id', + ), + migrations.RemoveField( + model_name='layerversiondependency', + name='layer_source', + ), + migrations.RemoveField( + model_name='layerversiondependency', + name='up_id', + ), + migrations.RemoveField( + model_name='machine', + name='layer_source', + ), + migrations.RemoveField( + model_name='machine', + name='up_id', + ), + ] diff --git a/bitbake/lib/toaster/orm/migrations/0011_delete_layersource.py b/bitbake/lib/toaster/orm/migrations/0011_delete_layersource.py new file mode 100644 index 0000000000..75506961a9 --- /dev/null +++ b/bitbake/lib/toaster/orm/migrations/0011_delete_layersource.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0010_delete_layer_source_references'), + ] + + operations = [ + migrations.DeleteModel( + name='LayerSource', + ), + ] diff --git a/bitbake/lib/toaster/orm/migrations/0012_use_release_instead_of_up_branch.py b/bitbake/lib/toaster/orm/migrations/0012_use_release_instead_of_up_branch.py new file mode 100644 index 0000000000..0e6bb83311 --- /dev/null +++ b/bitbake/lib/toaster/orm/migrations/0012_use_release_instead_of_up_branch.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.db.models import Q + + +def branch_to_release(apps, schema_editor): + Layer_Version = apps.get_model('orm', 'Layer_Version') + Release = apps.get_model('orm', 'Release') + + print("Converting all layer version up_branches to releases") + # Find all the layer versions which have an upbranch and convert them to + # the release that they're for. + for layer_version in Layer_Version.objects.filter( + Q(release=None) & ~Q(up_branch=None)): + try: + # HEAD and local are equivalent + if "HEAD" in layer_version.up_branch.name: + release = Release.objects.get(name="local") + layer_version.commit = "HEAD" + layer_version.branch = "HEAD" + else: + release = Release.objects.get( + name=layer_version.up_branch.name) + + layer_version.release = release + layer_version.save() + except Exception as e: + print("Couldn't work out an appropriate release for %s " + "the up_branch was %s " + "user the django admin interface to correct it" % + (layer_version.layer.name, layer_version.up_branch.name)) + print(e) + + continue + + +class Migration(migrations.Migration): + + dependencies = [ + ('orm', '0011_delete_layersource'), + ] + + operations = [ + migrations.AddField( + model_name='layer_version', + name='release', + field=models.ForeignKey(to='orm.Release', default=None, null=True), + ), + migrations.RunPython(branch_to_release, + reverse_code=migrations.RunPython.noop), + + migrations.RemoveField( + model_name='layer_version', + name='up_branch', + ), + + migrations.DeleteModel( + name='Branch', + ), + ] diff --git a/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py new file mode 100644 index 0000000000..14e9c15648 --- /dev/null +++ b/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py @@ -0,0 +1,218 @@ +#! /usr/bin/env python +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Toaster Implementation +# +# Copyright (C) 2013-2016 Intel Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# The Wait class and some of SeleniumDriverHelper and SeleniumTestCase are +# modified from Patchwork, released under the same licence terms as Toaster: +# https://github.com/dlespiau/patchwork/blob/master/patchwork/tests.browser.py + +""" +Helper methods for creating Toaster Selenium tests which run within +the context of Django unit tests. +""" + +import os +import time +import unittest + +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium import webdriver +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.common.exceptions import NoSuchElementException, \ + StaleElementReferenceException, TimeoutException + +def create_selenium_driver(browser='chrome'): + # set default browser string based on env (if available) + env_browser = os.environ.get('TOASTER_TESTS_BROWSER') + if env_browser: + browser = env_browser + + if browser == 'chrome': + return webdriver.Chrome( + service_args=["--verbose", "--log-path=selenium.log"] + ) + elif browser == 'firefox': + return webdriver.Firefox() + elif browser == 'marionette': + capabilities = DesiredCapabilities.FIREFOX + capabilities['marionette'] = True + return webdriver.Firefox(capabilities=capabilities) + elif browser == 'ie': + return webdriver.Ie() + elif browser == 'phantomjs': + return webdriver.PhantomJS() + else: + msg = 'Selenium driver for browser %s is not available' % browser + raise RuntimeError(msg) + +class Wait(WebDriverWait): + """ + Subclass of WebDriverWait with predetermined timeout and poll + frequency. Also deals with a wider variety of exceptions. + """ + _TIMEOUT = 10 + _POLL_FREQUENCY = 0.5 + + def __init__(self, driver): + super(Wait, self).__init__(driver, self._TIMEOUT, self._POLL_FREQUENCY) + + def until(self, method, message=''): + """ + Calls the method provided with the driver as an argument until the + return value is not False. + """ + + end_time = time.time() + self._timeout + while True: + try: + value = method(self._driver) + if value: + return value + except NoSuchElementException: + pass + except StaleElementReferenceException: + pass + + time.sleep(self._poll) + if time.time() > end_time: + break + + raise TimeoutException(message) + + def until_not(self, method, message=''): + """ + Calls the method provided with the driver as an argument until the + return value is False. + """ + + end_time = time.time() + self._timeout + while True: + try: + value = method(self._driver) + if not value: + return value + except NoSuchElementException: + return True + except StaleElementReferenceException: + pass + + time.sleep(self._poll) + if time.time() > end_time: + break + + raise TimeoutException(message) + +class SeleniumTestCaseBase(unittest.TestCase): + """ + NB StaticLiveServerTestCase is used as the base test case so that + static files are served correctly in a Selenium test run context; see + https://docs.djangoproject.com/en/1.9/ref/contrib/staticfiles/#specialized-test-case-to-support-live-testing + """ + + @classmethod + def setUpClass(cls): + """ Create a webdriver driver at the class level """ + + super(SeleniumTestCaseBase, cls).setUpClass() + + # instantiate the Selenium webdriver once for all the test methods + # in this test case + cls.driver = create_selenium_driver() + cls.driver.maximize_window() + + @classmethod + def tearDownClass(cls): + """ Clean up webdriver driver """ + + cls.driver.quit() + super(SeleniumTestCaseBase, cls).tearDownClass() + + def get(self, url): + """ + Selenium requires absolute URLs, so convert Django URLs returned + by resolve() or similar to absolute ones and get using the + webdriver instance. + + url: a relative URL + """ + abs_url = '%s%s' % (self.live_server_url, url) + self.driver.get(abs_url) + + def find(self, selector): + """ Find single element by CSS selector """ + return self.driver.find_element_by_css_selector(selector) + + def find_all(self, selector): + """ Find all elements matching CSS selector """ + return self.driver.find_elements_by_css_selector(selector) + + def element_exists(self, selector): + """ + Return True if one element matching selector exists, + False otherwise + """ + return len(self.find_all(selector)) == 1 + + def focused_element(self): + """ Return the element which currently has focus on the page """ + return self.driver.switch_to.active_element + + def wait_until_present(self, selector): + """ Wait until element matching CSS selector is on the page """ + is_present = lambda driver: self.find(selector) + msg = 'An element matching "%s" should be on the page' % selector + element = Wait(self.driver).until(is_present, msg) + return element + + def wait_until_visible(self, selector): + """ Wait until element matching CSS selector is visible on the page """ + is_visible = lambda driver: self.find(selector).is_displayed() + msg = 'An element matching "%s" should be visible' % selector + Wait(self.driver).until(is_visible, msg) + return self.find(selector) + + def wait_until_focused(self, selector): + """ Wait until element matching CSS selector has focus """ + is_focused = \ + lambda driver: self.find(selector) == self.focused_element() + msg = 'An element matching "%s" should be focused' % selector + Wait(self.driver).until(is_focused, msg) + return self.find(selector) + + def enter_text(self, selector, value): + """ Insert text into element matching selector """ + # note that keyup events don't occur until the element is clicked + # (in the case of , for example), so simulate + # user clicking the element before inserting text into it + field = self.click(selector) + + field.send_keys(value) + return field + + def click(self, selector): + """ Click on element which matches CSS selector """ + element = self.wait_until_visible(selector) + element.click() + return element + + def get_page_source(self): + """ Get raw HTML for the current page """ + return self.driver.page_source