diff options
author | Elliot Smith <elliot.smith@intel.com> | 2016-04-19 17:28:47 +0100 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2016-04-19 21:11:26 +0100 |
commit | d9dd864c68bf968fb50ff96b6545dc40d608f9df (patch) | |
tree | a488f3e4997eac608c2e4ec35be44ba1f221f940 /bitbake | |
parent | 1cf8f215b3543ce0f39ebd3321d58cfb518f1c1f (diff) | |
download | openembedded-core-contrib-d9dd864c68bf968fb50ff96b6545dc40d608f9df.tar.gz |
bitbake: toaster-tests: tests for build dashboard
Convert existing tests to Selenium.
Add basic tests to check that the modal contains radio buttons to select
a custom image to edit when a build built multiple custom images, and
to create a new custom image from one of the images built during
the build.
[YOCTO #9123]
(Bitbake rev: c07f65feaba50b13a38635bd8149804c823d446a)
Signed-off-by: Elliot Smith <elliot.smith@intel.com>
Signed-off-by: Michael Wood <michael.g.wood@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake')
-rw-r--r-- | bitbake/lib/toaster/tests/browser/test_builddashboard_page.py | 251 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/tests.py | 87 |
2 files changed, 251 insertions, 87 deletions
diff --git a/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py new file mode 100644 index 0000000000..5e08749470 --- /dev/null +++ b/bitbake/lib/toaster/tests/browser/test_builddashboard_page.py @@ -0,0 +1,251 @@ +#! /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. + +from django.core.urlresolvers import reverse +from django.utils import timezone + +from selenium_helpers import SeleniumTestCase + +from orm.models import Project, Release, BitbakeVersion, Build, LogMessage +from orm.models import Layer, Layer_Version, Recipe, CustomImageRecipe + +class TestBuildDashboardPage(SeleniumTestCase): + """ Tests for the build dashboard /build/X """ + + def setUp(self): + bbv = BitbakeVersion.objects.create(name='bbv1', giturl='/tmp/', + branch='master', dirpath="") + release = Release.objects.create(name='release1', + bitbake_version=bbv) + project = Project.objects.create_project(name='test project', + release=release) + + now = timezone.now() + + self.build1 = Build.objects.create(project=project, + started_on=now, + completed_on=now) + + self.build2 = Build.objects.create(project=project, + started_on=now, + completed_on=now) + + # exception + msg1 = 'an exception was thrown' + self.exception_message = LogMessage.objects.create( + build=self.build1, + level=LogMessage.EXCEPTION, + message=msg1 + ) + + # critical + msg2 = 'a critical error occurred' + self.critical_message = LogMessage.objects.create( + build=self.build1, + level=LogMessage.CRITICAL, + message=msg2 + ) + + # recipes related to the build, for testing the edit custom image/new + # custom image buttons + layer = Layer.objects.create(name='alayer') + layer_version = Layer_Version.objects.create( + layer=layer, build=self.build1 + ) + + # image recipes + self.image_recipe1 = Recipe.objects.create( + name='recipeA', + layer_version=layer_version, + file_path='/foo/recipeA.bb', + is_image=True + ) + self.image_recipe2 = Recipe.objects.create( + name='recipeB', + layer_version=layer_version, + file_path='/foo/recipeB.bb', + is_image=True + ) + + # custom image recipes for this project + self.custom_image_recipe1 = CustomImageRecipe.objects.create( + name='customRecipeY', + project=project, + layer_version=layer_version, + file_path='/foo/customRecipeY.bb', + base_recipe=self.image_recipe1, + is_image=True + ) + self.custom_image_recipe2 = CustomImageRecipe.objects.create( + name='customRecipeZ', + project=project, + layer_version=layer_version, + file_path='/foo/customRecipeZ.bb', + base_recipe=self.image_recipe2, + is_image=True + ) + + # custom image recipe for a different project (to test filtering + # of image recipes and custom image recipes is correct: this shouldn't + # show up in either query against self.build1) + self.custom_image_recipe3 = CustomImageRecipe.objects.create( + name='customRecipeOmega', + project=Project.objects.create(name='baz', release=release), + layer_version=Layer_Version.objects.create( + layer=layer, build=self.build2 + ), + file_path='/foo/customRecipeOmega.bb', + base_recipe=self.image_recipe2, + is_image=True + ) + + # another non-image recipe (to test filtering of image recipes and + # custom image recipes is correct: this shouldn't show up in either + # for any build) + self.non_image_recipe = Recipe.objects.create( + name='nonImageRecipe', + layer_version=layer_version, + file_path='/foo/nonImageRecipe.bb', + is_image=False + ) + + def _get_build_dashboard(self, build): + """ + Navigate to the build dashboard for build + """ + url = reverse('builddashboard', args=(build.id,)) + self.get(url) + + def _get_build_dashboard_errors(self, build): + """ + Get a list of HTML fragments representing the errors on the + dashboard for the Build object build + """ + self._get_build_dashboard(build) + return self.find_all('#errors div.alert-error') + + def _check_for_log_message(self, build, log_message): + """ + Check whether the LogMessage instance <log_message> is + represented as an HTML error in the dashboard page for the Build object + build + """ + errors = self._get_build_dashboard_errors(build) + self.assertEqual(len(errors), 2) + + expected_text = log_message.message + expected_id = str(log_message.id) + + found = False + for error in errors: + error_text = error.find_element_by_tag_name('pre').text + text_matches = (error_text == expected_text) + + error_id = error.get_attribute('data-error') + id_matches = (error_id == expected_id) + + if text_matches and id_matches: + found = True + break + + template_vars = (expected_text, error_text, + expected_id, error_id) + assertion_error_msg = 'exception not found as error: ' \ + 'expected text "%s" and got "%s"; ' \ + 'expected ID %s and got %s' % template_vars + self.assertTrue(found, assertion_error_msg) + + def _check_labels_in_modal(self, modal, expected): + """ + Check that the text values of the <label> elements inside + the WebElement modal match the list of text values in expected + """ + # labels containing the radio buttons we're testing for + labels = modal.find_elements_by_tag_name('label') + + # because the label content has the structure + # label text + # <input...> + # we have to regex on its innerHTML, as we can't just retrieve the + # "label text" on its own via the Selenium API + labels_text = sorted(map( + lambda label: label.get_attribute('innerHTML'), labels + )) + + expected = sorted(expected) + + self.assertEqual(len(labels_text), len(expected)) + + for idx, label_text in enumerate(labels_text): + self.assertRegexpMatches(label_text, expected[idx]) + + def test_exceptions_show_as_errors(self): + """ + LogMessages with level EXCEPTION should display in the errors + section of the page + """ + self._check_for_log_message(self.build1, self.exception_message) + + def test_criticals_show_as_errors(self): + """ + LogMessages with level CRITICAL should display in the errors + section of the page + """ + self._check_for_log_message(self.build1, self.critical_message) + + def test_edit_custom_image_button(self): + """ + A build which built two custom images should present a modal which lets + the user choose one of them to edit + """ + self._get_build_dashboard(self.build1) + modal = self.driver.find_element_by_id('edit-custom-image-modal') + + # recipes we expect to see in the edit custom image modal + expected_recipes = [ + self.custom_image_recipe1.name, + self.custom_image_recipe2.name + ] + + self._check_labels_in_modal(modal, expected_recipes) + + def test_new_custom_image_button(self): + """ + Check that a build with multiple images and custom images presents + all of them as options for creating a new custom image from + """ + self._get_build_dashboard(self.build1) + + # click the "new custom image" button, which populates the modal + selector = '[data-role="new-custom-image-trigger"] button' + self.click(selector) + + modal = self.driver.find_element_by_id('new-custom-image-modal') + + # recipes we expect to see in the new custom image modal + expected_recipes = [ + self.image_recipe1.name, + self.image_recipe2.name, + self.custom_image_recipe1.name, + self.custom_image_recipe2.name + ] + + self._check_labels_in_modal(modal, expected_recipes) diff --git a/bitbake/lib/toaster/toastergui/tests.py b/bitbake/lib/toaster/toastergui/tests.py index eebd1b79ba..a4cab58483 100644 --- a/bitbake/lib/toaster/toastergui/tests.py +++ b/bitbake/lib/toaster/toastergui/tests.py @@ -492,90 +492,3 @@ class ViewTests(TestCase): page_two_data, "Changed page on table %s but first row is the " "same as the previous page" % name) - -class BuildDashboardTests(TestCase): - """ Tests for the build dashboard /build/X """ - - def setUp(self): - bbv = BitbakeVersion.objects.create(name="bbv1", giturl="/tmp/", - branch="master", dirpath="") - release = Release.objects.create(name="release1", - bitbake_version=bbv) - project = Project.objects.create_project(name=PROJECT_NAME, - release=release) - - now = timezone.now() - - self.build1 = Build.objects.create(project=project, - started_on=now, - completed_on=now) - - # exception - msg1 = 'an exception was thrown' - self.exception_message = LogMessage.objects.create( - build=self.build1, - level=LogMessage.EXCEPTION, - message=msg1 - ) - - # critical - msg2 = 'a critical error occurred' - self.critical_message = LogMessage.objects.create( - build=self.build1, - level=LogMessage.CRITICAL, - message=msg2 - ) - - def _get_build_dashboard_errors(self): - """ - Get a list of HTML fragments representing the errors on the - build dashboard - """ - url = reverse('builddashboard', args=(self.build1.id,)) - response = self.client.get(url) - soup = BeautifulSoup(response.content) - return soup.select('#errors div.alert-error') - - def _check_for_log_message(self, log_message): - """ - Check whether the LogMessage instance <log_message> is - represented as an HTML error in the build dashboard page - """ - errors = self._get_build_dashboard_errors() - self.assertEqual(len(errors), 2) - - expected_text = log_message.message - expected_id = str(log_message.id) - - found = False - for error in errors: - error_text = error.find('pre').text - text_matches = (error_text == expected_text) - - error_id = error['data-error'] - id_matches = (error_id == expected_id) - - if text_matches and id_matches: - found = True - break - - template_vars = (expected_text, error_text, - expected_id, error_id) - assertion_error_msg = 'exception not found as error: ' \ - 'expected text "%s" and got "%s"; ' \ - 'expected ID %s and got %s' % template_vars - self.assertTrue(found, assertion_error_msg) - - def test_exceptions_show_as_errors(self): - """ - LogMessages with level EXCEPTION should display in the errors - section of the page - """ - self._check_for_log_message(self.exception_message) - - def test_criticals_show_as_errors(self): - """ - LogMessages with level CRITICAL should display in the errors - section of the page - """ - self._check_for_log_message(self.critical_message) |