123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- #! /usr/bin/env python3
- #
- # BitBake Toaster Implementation
- #
- # Copyright (C) 2013-2016 Intel Corporation
- #
- # SPDX-License-Identifier: GPL-2.0-only
- #
- from django.urls import reverse
- from django.utils import timezone
- from tests.browser.selenium_helpers import SeleniumTestCase
- from orm.models import Project, Release, BitbakeVersion, Build, LogMessage
- from orm.models import Layer, Layer_Version, Recipe, CustomImageRecipe, Variable
- 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,
- outcome=Build.SUCCEEDED)
- self.build2 = Build.objects.create(project=project,
- started_on=now,
- completed_on=now,
- outcome=Build.SUCCEEDED)
- self.build3 = Build.objects.create(project=project,
- started_on=now,
- completed_on=now,
- outcome=Build.FAILED)
- # add Variable objects to the successful builds, as this is the criterion
- # used to determine whether the left-hand panel should be displayed
- Variable.objects.create(build=self.build1,
- variable_name='Foo',
- variable_value='Bar')
- Variable.objects.create(build=self.build2,
- variable_name='Foo',
- variable_value='Bar')
- # 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
- )
- # error on the failed build
- msg3 = 'an error occurred'
- self.error_message = LogMessage.objects.create(
- build=self.build3,
- level=LogMessage.ERROR,
- message=msg3
- )
- # warning on the failed build
- msg4 = 'DANGER WILL ROBINSON'
- self.warning_message = LogMessage.objects.create(
- build=self.build3,
- level=LogMessage.WARNING,
- message=msg4
- )
- # 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
- )
- # non-image recipes related to a build, for testing the new custom
- # image button
- layer_version2 = Layer_Version.objects.create(layer=layer,
- build=self.build3)
- # 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-danger')
- def _check_for_log_message(self, message_elements, log_message):
- """
- Check that the LogMessage <log_message> has a representation in
- the HTML elements <message_elements>.
- message_elements: WebElements representing the log messages shown
- in the build dashboard; each should have a <pre> element inside
- it with a data-log-message-id attribute
- log_message: orm.models.LogMessage instance
- """
- expected_text = log_message.message
- expected_pk = str(log_message.pk)
- found = False
- for element in message_elements:
- log_message_text = element.find_element_by_tag_name('pre').text.strip()
- text_matches = (log_message_text == expected_text)
- log_message_pk = element.get_attribute('data-log-message-id')
- id_matches = (log_message_pk == expected_pk)
- if text_matches and id_matches:
- found = True
- break
- template_vars = (expected_text, expected_pk)
- assertion_failed_msg = 'message not found: ' \
- 'expected text "%s" and ID %s' % template_vars
- self.assertTrue(found, assertion_failed_msg)
- def _check_for_error_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._check_for_log_message(errors, log_message)
- 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_css_selector(".radio")
- labels_text = [lab.text for lab in labels]
- self.assertEqual(len(labels_text), len(expected))
- for expected_text in expected:
- self.assertTrue(expected_text in labels_text,
- "Could not find %s in %s" % (expected_text,
- labels_text))
- def test_exceptions_show_as_errors(self):
- """
- LogMessages with level EXCEPTION should display in the errors
- section of the page
- """
- self._check_for_error_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_error_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)
- # click the "edit custom image" button, which populates the modal
- selector = '[data-role="edit-custom-image-trigger"]'
- self.click(selector)
- modal = self.driver.find_element_by_id('edit-custom-image-modal')
- self.wait_until_visible("#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"]'
- self.click(selector)
- modal = self.driver.find_element_by_id('new-custom-image-modal')
- self.wait_until_visible("#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)
- def test_new_custom_image_button_no_image(self):
- """
- Check that a build which builds non-image recipes doesn't show
- the new custom image button on the dashboard.
- """
- self._get_build_dashboard(self.build3)
- selector = '[data-role="new-custom-image-trigger"]'
- self.assertFalse(self.element_exists(selector),
- 'new custom image button should not show for builds which ' \
- 'don\'t have any image recipes')
- def test_left_panel(self):
- """"
- Builds which succeed should have a left panel and a build summary
- """
- self._get_build_dashboard(self.build1)
- left_panel = self.find_all('#nav')
- self.assertEqual(len(left_panel), 1)
- build_summary = self.find_all('[data-role="build-summary-heading"]')
- self.assertEqual(len(build_summary), 1)
- def test_failed_no_left_panel(self):
- """
- Builds which fail should have no left panel and no build summary
- """
- self._get_build_dashboard(self.build3)
- left_panel = self.find_all('#nav')
- self.assertEqual(len(left_panel), 0)
- build_summary = self.find_all('[data-role="build-summary-heading"]')
- self.assertEqual(len(build_summary), 0)
- def test_failed_shows_errors_and_warnings(self):
- """
- Failed builds should still show error and warning messages
- """
- self._get_build_dashboard(self.build3)
- errors = self.find_all('#errors div.alert-danger')
- self._check_for_log_message(errors, self.error_message)
- # expand the warnings area
- self.click('#warning-toggle')
- self.wait_until_visible('#warnings div.alert-warning')
- warnings = self.find_all('#warnings div.alert-warning')
- self._check_for_log_message(warnings, self.warning_message)
|