diff options
Diffstat (limited to 'yocto-poky/bitbake/lib/toaster/toastergui/tests.py')
-rw-r--r-- | yocto-poky/bitbake/lib/toaster/toastergui/tests.py | 859 |
1 files changed, 213 insertions, 646 deletions
diff --git a/yocto-poky/bitbake/lib/toaster/toastergui/tests.py b/yocto-poky/bitbake/lib/toaster/toastergui/tests.py index 9e6c46a25..a4cab5848 100644 --- a/yocto-poky/bitbake/lib/toaster/toastergui/tests.py +++ b/yocto-poky/bitbake/lib/toaster/toastergui/tests.py @@ -30,90 +30,45 @@ from orm.models import Project, Release, BitbakeVersion, Package, LogMessage from orm.models import ReleaseLayerSourcePriority, LayerSource, Layer, Build from orm.models import Layer_Version, Recipe, Machine, ProjectLayer, Target from orm.models import CustomImageRecipe, ProjectVariable -from orm.models import Branch +from orm.models import Branch, CustomImagePackage import toastermain +import inspect +import toastergui from toastergui.tables import SoftwareRecipesTable import json +from datetime import timedelta from bs4 import BeautifulSoup import re +import string +import json PROJECT_NAME = "test project" +PROJECT_NAME2 = "test project 2" CLI_BUILDS_PROJECT_NAME = 'Command line builds' -# by default, tests are run in build mode; to run in analysis mode, -# set this to False in individual test cases -toastermain.settings.BUILD_MODE = True - class ViewTests(TestCase): """Tests to verify view APIs.""" - def setUp(self): - bbv = BitbakeVersion.objects.create(name="test bbv", giturl="/tmp/", - branch="master", dirpath="") - release = Release.objects.create(name="test release", - branch_name="master", - bitbake_version=bbv) - self.project = Project.objects.create_project(name=PROJECT_NAME, - release=release) - now = timezone.now() - - build = Build.objects.create(project=self.project, - started_on=now, - completed_on=now) - - layersrc = LayerSource.objects.create(sourcetype=LayerSource.TYPE_IMPORTED) - self.priority = ReleaseLayerSourcePriority.objects.create(release=release, - layer_source=layersrc) - layer = Layer.objects.create(name="base-layer", layer_source=layersrc, - vcs_url="/tmp/") - - branch = Branch.objects.create(name="master", layer_source=layersrc) - - lver = Layer_Version.objects.create(layer=layer, project=self.project, - layer_source=layersrc, commit="master", - up_branch=branch) - - self.recipe1 = Recipe.objects.create(layer_source=layersrc, - name="base-recipe", - version="1.2", - summary="one recipe", - description="recipe", - layer_version=lver) - - Machine.objects.create(layer_version=lver, name="wisk", - description="wisking machine") - - ProjectLayer.objects.create(project=self.project, layercommit=lver) - - - self.customr = CustomImageRecipe.objects.create(\ - name="custom recipe", project=self.project, - base_recipe=self.recipe1) - - self.package = Package.objects.create(name='pkg1', recipe=self.recipe1, - build=build) + fixtures = ['toastergui-unittest-data'] + def setUp(self): - # recipe with project for testing AvailableRecipe table - self.recipe2 = Recipe.objects.create(layer_source=layersrc, - name="fancy-recipe", - version="1.4", - summary="a fancy recipe", - description="fancy recipe", - layer_version=lver, - file_path='/home/foo') - - self.assertTrue(lver in self.project.compatible_layerversions()) + self.project = Project.objects.first() + self.recipe1 = Recipe.objects.get(pk=2) + self.recipe2 = Recipe.objects.last() + self.customr = CustomImageRecipe.objects.first() + self.cust_package = CustomImagePackage.objects.first() + self.package = Package.objects.first() + self.lver = Layer_Version.objects.first() def test_get_base_call_returns_html(self): """Basic test for all-projects view""" response = self.client.get(reverse('all-projects'), follow=True) self.assertEqual(response.status_code, 200) self.assertTrue(response['Content-Type'].startswith('text/html')) - self.assertTemplateUsed(response, "projects.html") - self.assertTrue(PROJECT_NAME in response.content) + self.assertTemplateUsed(response, "projects-toastertable.html") def test_get_json_call_returns_json(self): """Test for all projects output in json format""" @@ -128,16 +83,9 @@ class ViewTests(TestCase): self.assertEqual(data["error"], "ok") self.assertTrue("rows" in data) - self.assertTrue(PROJECT_NAME in [x["name"] for x in data["rows"]]) + self.assertTrue(self.project.name in [x["name"] for x in data["rows"]]) self.assertTrue("id" in data["rows"][0]) - self.assertEqual(sorted(data["rows"][0]), - ['bitbake_version_id', 'created', 'id', - 'is_default', 'layersTypeAheadUrl', 'name', - 'num_builds', 'projectBuildsUrl', 'projectPageUrl', - 'recipesTypeAheadUrl', 'release_id', - 'short_description', 'updated', 'user_id']) - def test_typeaheads(self): """Test typeahead ReST API""" layers_url = reverse('xhr_layerstypeahead', args=(self.project.id,)) @@ -181,7 +129,6 @@ class ViewTests(TestCase): return False - import string for url in urls: results = False @@ -198,15 +145,17 @@ class ViewTests(TestCase): def test_xhr_import_layer(self): """Test xhr_importlayer API""" + LayerSource.objects.create(sourcetype=LayerSource.TYPE_IMPORTED) #Test for importing an already existing layer args = {'vcs_url' : "git://git.example.com/test", 'name' : "base-layer", 'git_ref': "c12b9596afd236116b25ce26dbe0d793de9dc7ce", - 'project_id': 1, 'dir_path' : "/path/in/repository"} + 'project_id': self.project.id, + 'dir_path' : "/path/in/repository"} response = self.client.post(reverse('xhr_importlayer'), args) data = json.loads(response.content) self.assertEqual(response.status_code, 200) - self.assertNotEqual(data["error"], "ok") + self.assertEqual(data["error"], "ok") #Test to verify import of a layer successful args['name'] = "meta-oe" @@ -272,13 +221,12 @@ class ViewTests(TestCase): def test_xhr_custom_details(self): """Test getting custom recipe details""" - name = "custom recipe" url = reverse('xhr_customrecipe_id', args=(self.customr.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 200) expected = {"error": "ok", "info": {'id': self.customr.id, - 'name': name, + 'name': self.customr.name, 'base_recipe_id': self.recipe1.id, 'project_id': self.project.id, } @@ -290,7 +238,9 @@ class ViewTests(TestCase): name = "to be deleted" recipe = CustomImageRecipe.objects.create(\ name=name, project=self.project, - base_recipe=self.recipe1) + base_recipe=self.recipe1, + file_path="/tmp/testing", + layer_version=self.customr.layer_version) url = reverse('xhr_customrecipe_id', args=(recipe.id,)) response = self.client.delete(url) self.assertEqual(response.status_code, 200) @@ -303,20 +253,34 @@ class ViewTests(TestCase): def test_xhr_custom_packages(self): """Test adding and deleting package to a custom recipe""" - url = reverse('xhr_customrecipe_packages', - args=(self.customr.id, self.package.id)) - # add self.package1 to recipe - response = self.client.put(url) + # add self.package to recipe + response = self.client.put(reverse('xhr_customrecipe_packages', + args=(self.customr.id, + self.cust_package.id))) + self.assertEqual(response.status_code, 200) - self.assertEqual(json.loads(response.content), {"error": "ok"}) - self.assertEqual(self.customr.packages.all()[0].id, self.package.id) + self.assertEqual(json.loads(response.content), + {"error": "ok"}) + self.assertEqual(self.customr.appends_set.first().name, + self.cust_package.name) # delete it - response = self.client.delete(url) + to_delete = self.customr.appends_set.first().pk + del_url = reverse('xhr_customrecipe_packages', + args=(self.customr.id, to_delete)) + + response = self.client.delete(del_url) self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content), {"error": "ok"}) - self.assertFalse(self.customr.packages.all()) - # delete it again to test error condition - response = self.client.delete(url) + all_packages = self.customr.get_all_packages().values_list('pk', + flat=True) + + self.assertFalse(to_delete in all_packages) + # delete invalid package to test error condition + del_url = reverse('xhr_customrecipe_packages', + args=(self.customr.id, + 99999)) + + response = self.client.delete(del_url) self.assertEqual(response.status_code, 200) self.assertNotEqual(json.loads(response.content)["error"], "ok") @@ -332,6 +296,18 @@ class ViewTests(TestCase): self.assertNotEqual(json.loads(response.content), {"error": "ok"}) + def test_download_custom_recipe(self): + """Download the recipe file generated for the custom image""" + + # Create a dummy recipe file for the custom image generation to read + open("/tmp/a_recipe.bb", 'wa').close() + response = self.client.get(reverse('customrecipedownload', + args=(self.project.id, + self.customr.id))) + + self.assertEqual(response.status_code, 200) + + def test_software_recipes_table(self): """Test structure returned for Software RecipesTable""" table = SoftwareRecipesTable() @@ -344,7 +320,6 @@ class ViewTests(TestCase): row2 = next(x for x in rows if x['name'] == self.recipe2.name) self.assertEqual(response.status_code, 200, 'should be 200 OK status') - self.assertEqual(len(rows), 2, 'should be 2 recipes') # check other columns have been populated correctly self.assertEqual(row1['name'], self.recipe1.name) @@ -360,568 +335,160 @@ class ViewTests(TestCase): self.assertEqual(row2['layer_version__layer__name'], self.recipe2.layer_version.layer.name) -class LandingPageTests(TestCase): - """ Tests for redirects on the landing page """ - # disable bogus pylint message error: - # "Instance of 'WSGIRequest' has no 'url' member (no-member)" - # (see https://github.com/landscapeio/pylint-django/issues/42) - # pylint: disable=E1103 - - LANDING_PAGE_TITLE = 'This is Toaster' - - def setUp(self): - """ Add default project manually """ - self.project = Project.objects.create_project('foo', None) - self.project.is_default = True - self.project.save() - - def test_only_default_project(self): - """ - No projects except default - => get the landing page - """ - response = self.client.get(reverse('landing')) - self.assertTrue(self.LANDING_PAGE_TITLE in response.content) - - def test_default_project_has_build(self): - """ - Default project has a build, no other projects - => get the builds page - """ - now = timezone.now() - build = Build.objects.create(project=self.project, - started_on=now, - completed_on=now) - build.save() - - response = self.client.get(reverse('landing')) - self.assertEqual(response.status_code, 302, - 'response should be a redirect') - self.assertTrue('/builds' in response.url, - 'should redirect to builds') - - def test_user_project_exists(self): - """ - User has added a project (without builds) - => get the projects page - """ - user_project = Project.objects.create_project('foo', None) - user_project.save() - - response = self.client.get(reverse('landing')) - self.assertEqual(response.status_code, 302, - 'response should be a redirect') - self.assertTrue('/projects' in response.url, - 'should redirect to projects') - - def test_user_project_has_build(self): - """ - User has added a project (with builds) - => get the builds page - """ - user_project = Project.objects.create_project('foo', None) - user_project.save() - - now = timezone.now() - build = Build.objects.create(project=user_project, - started_on=now, - completed_on=now) - build.save() - - response = self.client.get(reverse('landing')) - self.assertEqual(response.status_code, 302, - 'response should be a redirect') - self.assertTrue('/builds' in response.url, - 'should redirect to builds') - -class AllProjectsPageTests(TestCase): - """ Tests for projects page /projects/ """ - - MACHINE_NAME = 'delorean' - - def setUp(self): - """ Add default project manually """ - project = Project.objects.create_project(CLI_BUILDS_PROJECT_NAME, None) - self.default_project = project - self.default_project.is_default = True - self.default_project.save() - - # this project is only set for some of the tests - self.project = None - - self.release = None - - def _add_build_to_default_project(self): - """ Add a build to the default project (not used in all tests) """ - now = timezone.now() - build = Build.objects.create(project=self.default_project, - started_on=now, - completed_on=now) - build.save() - - def _add_non_default_project(self): - """ Add another project """ - bbv = BitbakeVersion.objects.create(name="test bbv", giturl="/tmp/", - branch="master", dirpath="") - self.release = Release.objects.create(name="test release", - branch_name="master", - bitbake_version=bbv) - self.project = Project.objects.create_project(PROJECT_NAME, self.release) - self.project.is_default = False - self.project.save() - - # fake the MACHINE variable - project_var = ProjectVariable.objects.create(project=self.project, - name='MACHINE', - value=self.MACHINE_NAME) - project_var.save() - - def test_default_project_hidden(self): - """ The default project should be hidden if it has no builds """ - params = {"count": 10, "orderby": "updated:-", "page": 1} - response = self.client.get(reverse('all-projects'), params) - - self.assertTrue(not('tr class="data"' in response.content), - 'should be no project rows in the page') - self.assertTrue(not(CLI_BUILDS_PROJECT_NAME in response.content), - 'default project "cli builds" should not be in page') - - def test_default_project_has_build(self): - """ The default project should be shown if it has builds """ - self._add_build_to_default_project() - - params = {"count": 10, "orderby": "updated:-", "page": 1} - response = self.client.get(reverse('all-projects'), params) - - self.assertTrue('tr class="data"' in response.content, - 'should be a project row in the page') - self.assertTrue(CLI_BUILDS_PROJECT_NAME in response.content, - 'default project "cli builds" should be in page') - - def test_default_project_release(self): - """ - The release for the default project should display as - 'Not applicable' - """ - # need a build, otherwise project doesn't display at all - self._add_build_to_default_project() - - # another project to test, which should show release - self._add_non_default_project() - - response = self.client.get(reverse('all-projects'), follow=True) - soup = BeautifulSoup(response.content) - - # check the release cell for the default project - attrs = {'data-project': str(self.default_project.id)} - rows = soup.find_all('tr', attrs=attrs) - self.assertEqual(len(rows), 1, 'should be one row for default project') - cells = rows[0].find_all('td', attrs={'data-project-field': 'release'}) - self.assertEqual(len(cells), 1, 'should be one release cell') - text = cells[0].select('span.muted')[0].text - self.assertEqual(text, 'Not applicable', - 'release should be not applicable for default project') - - # check the link in the release cell for the other project - attrs = {'data-project': str(self.project.id)} - rows = soup.find_all('tr', attrs=attrs) - cells = rows[0].find_all('td', attrs={'data-project-field': 'release'}) - text = cells[0].select('a')[0].text - self.assertEqual(text, self.release.name, - 'release name should be shown for non-default project') - - def test_default_project_machine(self): - """ - The machine for the default project should display as - 'Not applicable' - """ - # need a build, otherwise project doesn't display at all - self._add_build_to_default_project() - - # another project to test, which should show machine - self._add_non_default_project() - - response = self.client.get(reverse('all-projects'), follow=True) - soup = BeautifulSoup(response.content) - - # check the machine cell for the default project - attrs = {'data-project': str(self.default_project.id)} - rows = soup.find_all('tr', attrs=attrs) - self.assertEqual(len(rows), 1, 'should be one row for default project') - cells = rows[0].find_all('td', attrs={'data-project-field': 'machine'}) - self.assertEqual(len(cells), 1, 'should be one machine cell') - text = cells[0].select('span.muted')[0].text - self.assertEqual(text, 'Not applicable', - 'machine should be not applicable for default project') - - # check the link in the machine cell for the other project - attrs = {'data-project': str(self.project.id)} - rows = soup.find_all('tr', attrs=attrs) - cells = rows[0].find_all('td', attrs={'data-project-field': 'machine'}) - text = cells[0].select('a')[0].text - self.assertEqual(text, self.MACHINE_NAME, - 'machine name should be shown for non-default project') - - def test_project_page_links(self): - """ - Test that links for the default project point to the builds - page /projects/X/builds for that project, and that links for - other projects point to their configuration pages /projects/X/ - """ - - # need a build, otherwise project doesn't display at all - self._add_build_to_default_project() - - # another project to test, which should show machine - self._add_non_default_project() - - response = self.client.get(reverse('all-projects'), follow=True) - soup = BeautifulSoup(response.content) - - # link for default project - row = soup.find('tr', attrs={'data-project': self.default_project.id}) - cell = row.find('td', attrs={'data-project-field': 'name'}) - expected_url = reverse('projectbuilds', args=(self.default_project.id,)) - self.assertEqual(cell.find('a')['href'], expected_url, - 'link on default project name should point to builds') - - # link for other project - row = soup.find('tr', attrs={'data-project': self.project.id}) - cell = row.find('td', attrs={'data-project-field': 'name'}) - expected_url = reverse('project', args=(self.project.id,)) - self.assertEqual(cell.find('a')['href'], expected_url, - 'link on project name should point to configuration') + def test_toaster_tables(self): + """Test all ToasterTables instances""" + current_recipes = self.project.get_available_recipes() + + def get_data(table, options={}): + """Send a request and parse the json response""" + options['format'] = "json" + options['nocache'] = "true" + request = RequestFactory().get('/', options) + + # This is the image recipe needed for a package list for + # PackagesTable do this here to throw a non exist exception + image_recipe = Recipe.objects.get(pk=4) + + # Add any kwargs that are needed by any of the possible tables + args = {'pid': self.project.id, + 'layerid': self.lver.pk, + 'recipeid': self.recipe1.pk, + 'recipe_id': image_recipe.pk, + 'custrecipeid': self.customr.pk + } -class ProjectBuildsPageTests(TestCase): - """ Test data at /project/X/builds is displayed correctly """ + response = table.get(request, **args) + return json.loads(response.content) + + # Get a list of classes in tables module + tables = inspect.getmembers(toastergui.tables, inspect.isclass) + + for name, table_cls in tables: + # Filter out the non ToasterTables from the tables module + if not issubclass(table_cls, toastergui.widgets.ToasterTable) or \ + table_cls == toastergui.widgets.ToasterTable: + continue + + # Get the table data without any options, this also does the + # initialisation of the table i.e. setup_columns, + # setup_filters and setup_queryset that we can use later + table = table_cls() + all_data = get_data(table) + + self.assertTrue(len(all_data['rows']) > 1, + "Cannot test on a %s table with < 1 row" % name) + + if table.default_orderby: + row_one = all_data['rows'][0][table.default_orderby.strip("-")] + row_two = all_data['rows'][1][table.default_orderby.strip("-")] + + if '-' in table.default_orderby: + self.assertTrue(row_one >= row_two, + "Default ordering not working on %s" + " '%s' should be >= '%s'" % + (name, row_one, row_two)) + else: + self.assertTrue(row_one <= row_two, + "Default ordering not working on %s" + " '%s' should be <= '%s'" % + (name, row_one, row_two)) + + # Test the column ordering and filtering functionality + for column in table.columns: + if column['orderable']: + # If a column is orderable test it in both order + # directions ordering on the columns field_name + ascending = get_data(table_cls(), + {"orderby" : column['field_name']}) + + row_one = ascending['rows'][0][column['field_name']] + row_two = ascending['rows'][1][column['field_name']] + + self.assertTrue(row_one <= row_two, + "Ascending sort applied but row 0 is less " + "than row 1 %s %s " % + (column['field_name'], name)) + + + descending = get_data(table_cls(), + {"orderby" : + '-'+column['field_name']}) + + row_one = descending['rows'][0][column['field_name']] + row_two = descending['rows'][1][column['field_name']] + + self.assertTrue(row_one >= row_two, + "Descending sort applied but row 0 is " + "greater than row 1 %s %s" % + (column['field_name'], name)) + + # If the two start rows are the same we haven't actually + # changed the order + self.assertNotEqual(ascending['rows'][0], + descending['rows'][0], + "An orderby %s has not changed the " + "order of the data in table %s" % + (column['field_name'], name)) + + if column['filter_name']: + # If a filter is available for the column get the filter + # info. This contains what filter actions are defined. + filter_info = get_data(table_cls(), + {"cmd": "filterinfo", + "name": column['filter_name']}) + self.assertTrue(len(filter_info['filter_actions']) > 0, + "Filter %s was defined but no actions " + "added to it" % column['filter_name']) + + for filter_action in filter_info['filter_actions']: + # filter string to pass as the option + # This is the name of the filter:action + # e.g. project_filter:not_in_project + filter_string = "%s:%s" % (column['filter_name'], + filter_action['action_name']) + # Now get the data with the filter applied + filtered_data = get_data(table_cls(), + {"filter" : filter_string}) + + # date range filter actions can't specify the + # number of results they return, so their count is 0 + if filter_action['count'] != None: + self.assertEqual(len(filtered_data['rows']), + int(filter_action['count']), + "We added a table filter for %s but " + "the number of rows returned was not " + "what the filter info said there " + "would be" % name) + + + # Test search functionality on the table + something_found = False + for search in list(string.ascii_letters): + search_data = get_data(table_cls(), {'search' : search}) + + if len(search_data['rows']) > 0: + something_found = True + break - def setUp(self): - bbv = BitbakeVersion.objects.create(name="bbv1", giturl="/tmp/", - branch="master", dirpath="") - release = Release.objects.create(name="release1", - bitbake_version=bbv) - self.project1 = Project.objects.create_project(name=PROJECT_NAME, - release=release) - self.project1.save() - - self.project2 = Project.objects.create_project(name=PROJECT_NAME, - release=release) - self.project2.save() - - self.default_project = Project.objects.create_project( - name=CLI_BUILDS_PROJECT_NAME, - release=release - ) - self.default_project.is_default = True - self.default_project.save() - - # parameters for builds to associate with the projects - now = timezone.now() - - self.project1_build_success = { - "project": self.project1, - "started_on": now, - "completed_on": now, - "outcome": Build.SUCCEEDED - } - - self.project1_build_in_progress = { - "project": self.project1, - "started_on": now, - "completed_on": now, - "outcome": Build.IN_PROGRESS - } - - self.project2_build_success = { - "project": self.project2, - "started_on": now, - "completed_on": now, - "outcome": Build.SUCCEEDED - } - - self.project2_build_in_progress = { - "project": self.project2, - "started_on": now, - "completed_on": now, - "outcome": Build.IN_PROGRESS - } - - def _get_rows_for_project(self, project_id): - """ Helper to retrieve HTML rows for a project """ - url = reverse("projectbuilds", args=(project_id,)) - response = self.client.get(url, follow=True) - soup = BeautifulSoup(response.content) - return soup.select('tr[class="data"]') - - def test_show_builds_for_project(self): - """ Builds for a project should be displayed """ - Build.objects.create(**self.project1_build_success) - Build.objects.create(**self.project1_build_success) - build_rows = self._get_rows_for_project(self.project1.id) - self.assertEqual(len(build_rows), 2) - - def test_show_builds_project_only(self): - """ Builds for other projects should be excluded """ - Build.objects.create(**self.project1_build_success) - Build.objects.create(**self.project1_build_success) - Build.objects.create(**self.project1_build_success) - - # shouldn't see these two - Build.objects.create(**self.project2_build_success) - Build.objects.create(**self.project2_build_in_progress) - - build_rows = self._get_rows_for_project(self.project1.id) - self.assertEqual(len(build_rows), 3) - - def test_builds_exclude_in_progress(self): - """ "in progress" builds should not be shown """ - Build.objects.create(**self.project1_build_success) - Build.objects.create(**self.project1_build_success) - - # shouldn't see this one - Build.objects.create(**self.project1_build_in_progress) - - # shouldn't see these two either, as they belong to a different project - Build.objects.create(**self.project2_build_success) - Build.objects.create(**self.project2_build_in_progress) - - build_rows = self._get_rows_for_project(self.project1.id) - self.assertEqual(len(build_rows), 2) - - def test_tasks_in_projectbuilds(self): - """ Task should be shown as suffix on build name """ - build = Build.objects.create(**self.project1_build_success) - Target.objects.create(build=build, target='bash', task='clean') - url = reverse("projectbuilds", args=(self.project1.id,)) - response = self.client.get(url, follow=True) - result = re.findall('^ +bash:clean$', response.content, re.MULTILINE) - self.assertEqual(len(result), 2) - - def test_cli_builds_hides_tabs(self): - """ - Display for command line builds should hide tabs; - note that the latest builds section is already tested in - AllBuildsPageTests, as the template is the same - """ - url = reverse("projectbuilds", args=(self.default_project.id,)) - response = self.client.get(url, follow=True) - soup = BeautifulSoup(response.content) - tabs = soup.select('#project-topbar') - self.assertEqual(len(tabs), 0, - 'should be no top bar shown for command line builds') - - def test_non_cli_builds_has_tabs(self): - """ - Non-command-line builds projects should show the tabs - """ - url = reverse("projectbuilds", args=(self.project1.id,)) - response = self.client.get(url, follow=True) - soup = BeautifulSoup(response.content) - tabs = soup.select('#project-topbar') - self.assertEqual(len(tabs), 1, - 'should be a top bar shown for non-command-line builds') - -class AllBuildsPageTests(TestCase): - """ Tests for all builds page /builds/ """ + self.assertTrue(something_found, + "We went through the whole alphabet and nothing" + " was found for the search of table %s" % name) - def setUp(self): - bbv = BitbakeVersion.objects.create(name="bbv1", giturl="/tmp/", - branch="master", dirpath="") - release = Release.objects.create(name="release1", - bitbake_version=bbv) - self.project1 = Project.objects.create_project(name=PROJECT_NAME, - release=release) - self.default_project = Project.objects.create_project( - name=CLI_BUILDS_PROJECT_NAME, - release=release - ) - self.default_project.is_default = True - self.default_project.save() - - # parameters for builds to associate with the projects - now = timezone.now() - - self.project1_build_success = { - "project": self.project1, - "started_on": now, - "completed_on": now, - "outcome": Build.SUCCEEDED - } - - self.default_project_build_success = { - "project": self.default_project, - "started_on": now, - "completed_on": now, - "outcome": Build.SUCCEEDED - } - - def test_show_tasks_in_allbuilds(self): - """ Task should be shown as suffix on build name """ - build = Build.objects.create(**self.project1_build_success) - Target.objects.create(build=build, target='bash', task='clean') - url = reverse('all-builds') - response = self.client.get(url, follow=True) - result = re.findall('bash:clean', response.content, re.MULTILINE) - self.assertEqual(len(result), 3) - - def test_no_run_again_for_cli_build(self): - """ "Run again" button should not be shown for command-line builds """ - build = Build.objects.create(**self.default_project_build_success) - url = reverse('all-builds') - response = self.client.get(url, follow=True) - soup = BeautifulSoup(response.content) - - attrs = {'data-latest-build-result': build.id} - result = soup.find('div', attrs=attrs) - - # shouldn't see a run again button for command-line builds - run_again_button = result.select('button') - self.assertEqual(len(run_again_button), 0) - - # should see a help icon for command-line builds - help_icon = result.select('i.get-help-green') - self.assertEqual(len(help_icon), 1) - - def test_tooltips_on_project_name(self): - """ - A tooltip should be present next to the command line - builds project name in the all builds page, but not for - other projects - """ - build1 = Build.objects.create(**self.project1_build_success) - default_build = Build.objects.create(**self.default_project_build_success) - - url = reverse('all-builds') - response = self.client.get(url, follow=True) - soup = BeautifulSoup(response.content) - - # no help icon on non-default project name - result = soup.find('tr', attrs={'data-table-build-result': build1.id}) - name = result.select('td.project-name')[0] - icons = name.select('i.get-help') - self.assertEqual(len(icons), 0, - 'should not be a help icon for non-cli builds name') - - # help icon on default project name - result = soup.find('tr', attrs={'data-table-build-result': default_build.id}) - name = result.select('td.project-name')[0] - icons = name.select('i.get-help') - self.assertEqual(len(icons), 1, - 'should be a help icon for cli builds name') - -class ProjectPageTests(TestCase): - """ Test project data at /project/X/ is displayed correctly """ - CLI_BUILDS_PROJECT_NAME = 'Command line builds' - - def test_command_line_builds_in_progress(self): - """ - In progress builds should not cause an error to be thrown - when navigating to "command line builds" project page; - see https://bugzilla.yoctoproject.org/show_bug.cgi?id=8277 - """ - - # add the "command line builds" default project; this mirrors what - # we do in migration 0026_set_default_project.py - default_project = Project.objects.create_project(self.CLI_BUILDS_PROJECT_NAME, None) - default_project.is_default = True - default_project.save() - - # add an "in progress" build for the default project - now = timezone.now() - build = Build.objects.create(project=default_project, - started_on=now, - completed_on=now, - outcome=Build.IN_PROGRESS) - - # navigate to the project page for the default project - url = reverse("project", args=(default_project.id,)) - response = self.client.get(url, follow=True) + # Test the limit functionality on the table + limited_data = get_data(table_cls(), {'limit' : "1"}) + self.assertEqual(len(limited_data['rows']), + 1, + "Limit 1 set on table %s but not 1 row returned" + % name) - self.assertEqual(response.status_code, 200) + # Test the pagination functionality on the table + page_one_data = get_data(table_cls(), {'limit' : "1", + "page": "1"})['rows'][0] -class BuildDashboardTests(TestCase): - """ Tests for the build dashboard /build/X """ + page_two_data = get_data(table_cls(), {'limit' : "1", + "page": "2"})['rows'][0] - 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) + self.assertNotEqual(page_one_data, + page_two_data, + "Changed page on table %s but first row is the " + "same as the previous page" % name) |