Toaster patchset
cover Toaster patchset
From: Alexandru DAMIAN <[email protected]>
Hi,
This is a Toaster patches with various fixes, especially in the testing area.
Can you please pull ?
Cheers,
Alex
The following changes since commit 702b42a47904f2378dd819e7463b3206883c2651:
data: Move warning code to the first loop for performance (2015-05-25 09:00:16 +0100)
are available in the git repository at:
git://git.yoctoproject.org/poky-contrib adamian/20150528-submission-bb
http://git.yoctoproject.org/cgit.cgi/poky-contrib/log/?h=adamian/20150528-submission-bb
Alexandru DAMIAN (12):
toaster: enable server-side sessions for all usages toaster logger: refactor recipe and layer file paths toaster logger: assign projects to all builds
toastergui: table header bring back
toaster: refactor checksettings command
toasterui: replace cookie-based preferences with session data
toastergui: tests for the all-projects API point
toastergui: add tests for xhr_datatypeahead calls
toaster: enable no browser start
bitbake/: toaster tts: fix tests
toaster tests: enable url check test
toaster: more HTML5 fixes
Ed Bartosh (3):
toastergui: show relative paths in configvars view
toaster-gui: Show task name for the builds in /builds
toastergui: Consider task name when restarting a build in /builds
Michael Wood (7):
toaster: Fix build button current project race
toaster: Fix toastertable header
toaster: Add a simplified ToasterTable template
toaster: Port layerdetails to ToasterTables
toaster: Add an invalidate cache method on data update signal
bldcontrol: Fix all failing unit tests
orm: Fix all failing unit test
bin/toaster | 12 +- lib/bb/ui/buildinfohelper.py | 84 ++- lib/toaster/bldcontrol/localhostbecontroller.py | 2 +- .../management/commands/checksettings.py | 26 +- lib/toaster/bldcontrol/models.py | 3 + lib/toaster/bldcontrol/sshbecontroller.py | 5 +- lib/toaster/bldcontrol/tests.py | 84 ++- lib/toaster/contrib/tts/backlog.txt | 2 - lib/toaster/contrib/tts/config.py | 21 + lib/toaster/contrib/tts/launcher.py | 2 +- lib/toaster/contrib/tts/runner.py | 4 +- lib/toaster/contrib/tts/tests.py | 55 +- .../tts/toasteruitest/toaster_automation_test.py | 14 +- lib/toaster/contrib/tts/urlcheck.py | 41 +- ...ion_local_path__add_field_recipe_pathflags__.py | 361 +++++++++++++ ...ect__chg_field_project_bitbake_version__chg_.py | 368 +++++++++++++ lib/toaster/orm/models.py | 58 +- lib/toaster/orm/tests.py | 32 +- lib/toaster/orm/views.py | 2 +- lib/toaster/toastergui/static/js/base.js | 5 +- lib/toaster/toastergui/static/js/layerdetails.js | 70 ++- lib/toaster/toastergui/static/js/libtoaster.js | 3 + lib/toaster/toastergui/static/js/table.js | 79 +-- lib/toaster/toastergui/tables.py | 82 ++- .../toastergui/templates/basetable_bottom.html | 4 - .../toastergui/templates/basetable_top.html | 9 +- lib/toaster/toastergui/templates/bpackage.html | 4 +- lib/toaster/toastergui/templates/brtargets.html | 20 + .../toastergui/templates/builddashboard.html | 2 +- .../toastergui/templates/configuration.html | 2 +- lib/toaster/toastergui/templates/configvars.html | 10 +- .../templates/detail_pagination_bottom.html | 5 - .../toastergui/templates/detail_sorted_header.html | 2 +- lib/toaster/toastergui/templates/layerdetails.html | 594 +++++++-------------- .../toastergui/templates/managed_mrb_section.html | 28 +- .../toastergui/templates/package_detail_base.html | 2 +- lib/toaster/toastergui/templates/recipe.html | 6 +- lib/toaster/toastergui/templates/recipes.html | 4 +- lib/toaster/toastergui/templates/runagain.html | 7 + lib/toaster/toastergui/templates/target.html | 2 +- .../toastergui/templates/toastertable-filter.html | 18 + .../toastergui/templates/toastertable-simple.html | 94 ++++ lib/toaster/toastergui/templates/toastertable.html | 53 +- lib/toaster/toastergui/templatetags/projecttags.py | 83 +-- lib/toaster/toastergui/tests.py | 78 +++ lib/toaster/toastergui/urls.py | 6 +- lib/toaster/toastergui/views.py | 133 ++--- lib/toaster/toastergui/widgets.py | 23 +- lib/toaster/toastermain/settings.py | 17 +- 49 files changed, 1778 insertions(+), 843 deletions(-) delete mode 100644 lib/toaster/contrib/tts/backlog.txt create mode 100644 lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py create mode 100644 lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py create mode 100644 lib/toaster/toastergui/templates/brtargets.html create mode 100644 lib/toaster/toastergui/templates/runagain.html create mode 100644 lib/toaster/toastergui/templates/toastertable-filter.html create mode 100644 lib/toaster/toastergui/templates/toastertable-simple.html create mode 100644 lib/toaster/toastergui/tests.py
Signature · 1 line
1.9.1
PATCH 1/22 toaster: Fix build button current project race
From: Michael Wood <[email protected]>
Make sure the current project value is set before we check to see if the project is buildable. Also update the blacklist url patterns where we aren't displaying the button.
[YOCTO #7739]
--- lib/toaster/toastergui/static/js/base.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
lib/toaster/toastergui/static/js/base.js +3 -2
@@ -6,11 +6,13 @@ function basePageInit (ctx) { /* Hide the button if we're on the project,newproject or importlyaer page * or if there are no projects yet defined */ - if (ctx.numProjects == 0 || ctx.currentUrl.search('newproject|project/\\d/$|importlayer/$') > 0){ + if (ctx.numProjects == 0 || ctx.currentUrl.search('newproject|project/\\d$|importlayer$') > 0){ newBuildButton.hide(); return; } + var currentProjectId = libtoaster.ctx.projectId; + /* Hide the change project icon when there is only one project */ if (ctx.numProjects == 1){ $('#project .icon-pencil').hide();
@@ -21,7 +23,6 @@ function basePageInit (ctx) { _checkProjectBuildable() _setupNewBuildButton(); - var currentProjectId = libtoaster.ctx.projectId; function _checkProjectBuildable(){ if (currentProjectId == undefined)
Signature · 1 line
1.9.1
PATCH 2/22 toaster: enable server-side sessions for all usages
From: Alexandru DAMIAN <[email protected]>
In an effort to fix client-side session storage using cookies, we enable server-side session support for all pages.
--- bin/toaster | 6 +++++- lib/toaster/toastergui/views.py | 6 ++++-- lib/toaster/toastermain/settings.py | 17 ++++++----------- 3 files changed, 15 insertions(+), 14 deletions(-)
bin/toaster +5 -1
@@ -53,7 +53,11 @@ function webserverStartAll() fi retval=0 - python $BBBASEDIR/lib/toaster/manage.py syncdb || retval=1 + if [ "$TOASTER_MANAGED" '==' '1' ]; then + python $BBBASEDIR/lib/toaster/manage.py syncdb || retval=1 + else + python $BBBASEDIR/lib/toaster/manage.py syncdb --noinput || retval=1 + fi python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=2 if [ $retval -eq 1 ]; then echo "Failed db sync, stopping system start" 1>&2
lib/toaster/toastergui/views.py +4 -2
@@ -266,8 +266,10 @@ def _get_queryset(model, queryset, filter_string, search_term, ordering_string, # if the value is given explicitly as a GET parameter it will be the first selected, # otherwise the cookie value will be used. def _get_parameters_values(request, default_count, default_order): - pagesize = request.GET.get('count', request.COOKIES.get('count', default_count)) - orderby = request.GET.get('orderby', request.COOKIES.get('orderby', default_order)) + from django.core.urlresolvers import resolve + current_url = resolve(request.path_info).url_name + pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count)) + orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order)) return (pagesize, orderby)
lib/toaster/toastermain/settings.py +6 -11
@@ -258,8 +258,13 @@ TEMPLATE_CONTEXT_PROCESSORS = ('django.contrib.auth.context_processors.auth', ) INSTALLED_APPS = ( - #'django.contrib.sites', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'django.contrib.sessions', + 'django.contrib.admin', 'django.contrib.staticfiles', + # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'django.contrib.humanize',
@@ -307,16 +312,6 @@ if os.environ.get('TOASTER_DEVEL', None) is not None: SOUTH_TESTS_MIGRATE = False -# if we run in managed mode, we need user support -if MANAGED: - INSTALLED_APPS = ('django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.messages', - 'django.contrib.sessions', - # Uncomment the next line to enable the admin: - 'django.contrib.admin', - ) + INSTALLED_APPS - # We automatically detect and install applications here if # they have a 'models.py' or 'views.py' file
Signature · 1 line
1.9.1
PATCH 3/22 toaster logger: refactor recipe and layer file paths
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This refactoring brings the "local_path" of the layer from the Layer object to the Layer_Version object, which is more appropriate as different checkouts of the same Layer may live in different directories.
This enables us to store Recipe file paths relative to a Layer_Version at all times, aleviating the need to store full file paths in the database. We also turn the prefix of the path (e.g. virtual:native path name space) into a pathflag field.
In turn, this solves the problem of mis-identification of tasks based on the recipe file paths, since we can also match the namespace of the file paths on the recipe files.
[YOCTO #7594]
--- lib/bb/ui/buildinfohelper.py | 47 ++- .../tts/toasteruitest/toaster_automation_test.py | 14 +- ...ion_local_path__add_field_recipe_pathflags__.py | 361 +++++++++++++++++++++ lib/toaster/orm/models.py | 23 +- lib/toaster/toastergui/templates/bpackage.html | 4 +- .../toastergui/templates/configuration.html | 2 +- .../toastergui/templates/package_detail_base.html | 2 +- lib/toaster/toastergui/templates/recipe.html | 4 +- lib/toaster/toastergui/templates/recipes.html | 4 +- lib/toaster/toastergui/templates/target.html | 2 +- lib/toaster/toastergui/views.py | 22 +- 11 files changed, 427 insertions(+), 58 deletions(-) create mode 100644 lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py
lib/bb/ui/buildinfohelper.py +32 -15
@@ -221,12 +221,12 @@ class ORMWrapper(object): def get_update_recipe_object(self, recipe_information, must_exist = False): assert 'layer_version' in recipe_information assert 'file_path' in recipe_information + assert 'pathflags' in recipe_information - if recipe_information['file_path'].startswith(recipe_information['layer_version'].layer.local_path): - recipe_information['file_path'] = recipe_information['file_path'][len(recipe_information['layer_version'].layer.local_path):].lstrip("/") + assert not recipe_information['file_path'].startswith("/") # we should have layer-relative paths at all times recipe_object, created = self._cached_get_or_create(Recipe, layer_version=recipe_information['layer_version'], - file_path=recipe_information['file_path']) + file_path=recipe_information['file_path'], pathflags = recipe_information['pathflags']) if created and must_exist: raise NotExisting("Recipe object created when expected to exist", recipe_information)
@@ -247,13 +247,15 @@ class ORMWrapper(object): assert 'branch' in layer_version_information assert 'commit' in layer_version_information assert 'priority' in layer_version_information + assert 'local_path' in layer_version_information layer_version_object, created = Layer_Version.objects.get_or_create( build = build_obj, layer = layer_obj, branch = layer_version_information['branch'], commit = layer_version_information['commit'], - priority = layer_version_information['priority'] + priority = layer_version_information['priority'], + local_path = layer_version_information['local_path'], ) self.layer_version_objects.append(layer_version_object)
@@ -262,13 +264,11 @@ class ORMWrapper(object): def get_update_layer_object(self, layer_information, brbe): assert 'name' in layer_information - assert 'local_path' in layer_information assert 'layer_index_url' in layer_information if brbe is None: layer_object, created = Layer.objects.get_or_create( name=layer_information['name'], - local_path=layer_information['local_path'], layer_index_url=layer_information['layer_index_url']) return layer_object else:
@@ -297,7 +297,6 @@ class ORMWrapper(object): for pl in buildrequest.project.projectlayer_set.filter(layercommit__layer__name = brl.name): if pl.layercommit.layer.vcs_url == brl.giturl : layer = pl.layercommit.layer - layer.local_path = layer_information['local_path'] layer.save() return layer
@@ -687,12 +686,12 @@ class BuildInfoHelper(object): if self.brbe is None: def _slkey_interactive(layer_version): assert isinstance(layer_version, Layer_Version) - return len(layer_version.layer.local_path) + return len(layer_version.local_path) # Heuristics: we always match recipe to the deepest layer path in the discovered layers for lvo in sorted(self.orm_wrapper.layer_version_objects, reverse=True, key=_slkey_interactive): # we can match to the recipe file path - if path.startswith(lvo.layer.local_path): + if path.startswith(lvo.local_path): return lvo else:
@@ -721,7 +720,7 @@ class BuildInfoHelper(object): logger.warn("Could not match layer version for recipe path %s : %s" % (path, self.orm_wrapper.layer_version_objects)) #mockup the new layer - unknown_layer, created = Layer.objects.get_or_create(name="__FIXME__unidentified_layer", local_path="/", layer_index_url="") + unknown_layer, created = Layer.objects.get_or_create(name="__FIXME__unidentified_layer", layer_index_url="") unknown_layer_version_obj, created = Layer_Version.objects.get_or_create(layer = unknown_layer, build = self.internal_state['build']) # append it so we don't run into this error again and again
@@ -731,11 +730,20 @@ class BuildInfoHelper(object): def _get_recipe_information_from_taskfile(self, taskfile): localfilepath = taskfile.split(":")[-1] + filepath_flags = ":".join(sorted(taskfile.split(":")[:-1])) layer_version_obj = self._get_layer_version_for_path(localfilepath) + + recipe_info = {} recipe_info['layer_version'] = layer_version_obj - recipe_info['file_path'] = taskfile + recipe_info['file_path'] = localfilepath + recipe_info['pathflags'] = filepath_flags + + if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path): + recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/") + else: + raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path)) return recipe_info
@@ -787,6 +795,7 @@ class BuildInfoHelper(object): for layer in layerinfos: try: self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)] = layerinfos[layer]['version'] + self.internal_state['lvs'][self.orm_wrapper.get_update_layer_object(layerinfos[layer], self.brbe)]['local_path'] = layerinfos[layer]['local_path'] except NotExisting as nee: logger.warn("buildinfohelper: cannot identify layer exception:%s " % nee)
@@ -899,8 +908,8 @@ class BuildInfoHelper(object): recipe_information = self._get_recipe_information_from_taskfile(taskfile) try: - if recipe_information['file_path'].startswith(recipe_information['layer_version'].layer.local_path): - recipe_information['file_path'] = recipe_information['file_path'][len(recipe_information['layer_version'].layer.local_path):].lstrip("/") + if recipe_information['file_path'].startswith(recipe_information['layer_version'].local_path): + recipe_information['file_path'] = recipe_information['file_path'][len(recipe_information['layer_version'].local_path):].lstrip("/") recipe_object = Recipe.objects.get(layer_version = recipe_information['layer_version'], file_path__endswith = recipe_information['file_path'],
@@ -1051,8 +1060,9 @@ class BuildInfoHelper(object): self.internal_state['recipes'] = {} for pn in event._depgraph['pn']: - file_name = event._depgraph['pn'][pn]['filename'] - layer_version_obj = self._get_layer_version_for_path(file_name.split(":")[-1]) + file_name = event._depgraph['pn'][pn]['filename'].split(":")[-1] + pathflags = ":".join(sorted(event._depgraph['pn'][pn]['filename'].split(":")[:-1])) + layer_version_obj = self._get_layer_version_for_path(file_name) assert layer_version_obj is not None
@@ -1082,6 +1092,13 @@ class BuildInfoHelper(object): recipe_info['bugtracker'] = event._depgraph['pn'][pn]['bugtracker'] recipe_info['file_path'] = file_name + recipe_info['pathflags'] = pathflags + + if recipe_info['file_path'].startswith(recipe_info['layer_version'].local_path): + recipe_info['file_path'] = recipe_info['file_path'][len(recipe_info['layer_version'].local_path):].lstrip("/") + else: + raise RuntimeError("Recipe file path %s is not under layer version at %s" % (recipe_info['file_path'], recipe_info['layer_version'].local_path)) + recipe = self.orm_wrapper.get_update_recipe_object(recipe_info) recipe.is_image = False if 'inherits' in event._depgraph['pn'][pn].keys():
lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py +7 -7
@@ -926,7 +926,7 @@ class toaster_cases(toaster_cases_base): self.driver.find_element_by_id("depends_on").click() self.driver.find_element_by_id("layer_version__branch").click() self.driver.find_element_by_id("layer_version__layer__commit").click() - self.driver.find_element_by_id("layer_version__layer__local_path").click() + self.driver.find_element_by_id("layer_version__local_path").click() self.driver.find_element_by_id("depends_by").click() self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
@@ -945,7 +945,7 @@ class toaster_cases(toaster_cases_base): table_head_dict = {'Recipe file':'recipe_file', 'Section':'recipe_section', \ 'License':'recipe_license', 'Layer':'layer_version__layer__name', \ - 'Layer branch':'layer_version__branch', 'Layer directory':'layer_version__layer__local_path'} + 'Layer branch':'layer_version__branch', 'Layer directory':'layer_version__local_path'} for key in table_head_dict: self.find_element_by_link_text_in_table(self.table_name, key).click() column_list = self.get_table_column_text("class", table_head_dict[key])
@@ -1066,7 +1066,7 @@ class toaster_cases(toaster_cases_base): self.driver.find_element_by_id("depends_on").click() self.driver.find_element_by_id("layer_version__branch").click() self.driver.find_element_by_id("layer_version__layer__commit").click() - self.driver.find_element_by_id("layer_version__layer__local_path").click() + self.driver.find_element_by_id("layer_version__local_path").click() self.driver.find_element_by_id("depends_by").click() self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click() # check if columns selected above is shown
@@ -1081,7 +1081,7 @@ class toaster_cases(toaster_cases_base): self.driver.find_element_by_id("depends_on").click() self.driver.find_element_by_id("layer_version__branch").click() self.driver.find_element_by_id("layer_version__layer__commit").click() - self.driver.find_element_by_id("layer_version__layer__local_path").click() + self.driver.find_element_by_id("layer_version__local_path").click() self.driver.find_element_by_id("depends_by").click() self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click() # don't exist any more
@@ -1425,7 +1425,7 @@ class toaster_cases(toaster_cases_base): # Step 4 # pulldown menu option_ids = ['recipe__layer_version__layer__name', 'recipe__layer_version__branch', \ - 'recipe__layer_version__layer__commit', 'recipe__layer_version__layer__local_path', \ + 'recipe__layer_version__layer__commit', 'recipe__layer_version__local_path', \ 'license', 'recipe__version'] self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click() for item in option_ids:
@@ -1470,7 +1470,7 @@ class toaster_cases(toaster_cases_base): self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click() self.driver.find_element_by_id("layer_version__branch").click() self.driver.find_element_by_id("layer_version__layer__commit").click() - self.driver.find_element_by_id("layer_version__layer__local_path").click() + self.driver.find_element_by_id("layer_version__local_path").click() self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click() # otable is the recipes table here otable_head_text = self.get_table_head_text('otable')
@@ -1488,7 +1488,7 @@ class toaster_cases(toaster_cases_base): self.driver.find_element_by_id("recipe__layer_version__layer__name").click() self.driver.find_element_by_id("recipe__layer_version__branch").click() self.driver.find_element_by_id("recipe__layer_version__layer__commit").click() - self.driver.find_element_by_id("recipe__layer_version__layer__local_path").click() + self.driver.find_element_by_id("recipe__layer_version__local_path").click() self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click() otable_head_text = self.get_table_head_text("otable") for item in ["Layer", "Layer branch", "Layer commit", "Layer directory"]:
lib/toaster/orm/migrations/0020_auto__add_field_layer_version_local_path__add_field_recipe_pathflags__.py new file +361 -0
361 lines not on this page · raw message
lib/toaster/orm/models.py +7 -16
@@ -457,7 +457,7 @@ class Task_Dependency(models.Model): depends_on = models.ForeignKey(Task, related_name='task_dependencies_depends') class Package(models.Model): - search_allowed_fields = ['name', 'version', 'revision', 'recipe__name', 'recipe__version', 'recipe__license', 'recipe__layer_version__layer__name', 'recipe__layer_version__branch', 'recipe__layer_version__commit', 'recipe__layer_version__layer__local_path', 'installed_name'] + search_allowed_fields = ['name', 'version', 'revision', 'recipe__name', 'recipe__version', 'recipe__license', 'recipe__layer_version__layer__name', 'recipe__layer_version__branch', 'recipe__layer_version__commit', 'recipe__layer_version__local_path', 'installed_name'] build = models.ForeignKey('Build') recipe = models.ForeignKey('Recipe', null=True) name = models.CharField(max_length=100)
@@ -528,7 +528,7 @@ class Package_File(models.Model): size = models.IntegerField() class Recipe(models.Model): - search_allowed_fields = ['name', 'version', 'file_path', 'section', 'summary', 'description', 'license', 'layer_version__layer__name', 'layer_version__branch', 'layer_version__commit', 'layer_version__layer__local_path', 'layer_version__layer_source__name'] + search_allowed_fields = ['name', 'version', 'file_path', 'section', 'summary', 'description', 'license', 'layer_version__layer__name', 'layer_version__branch', 'layer_version__commit', 'layer_version__local_path', 'layer_version__layer_source__name'] layer_source = models.ForeignKey('LayerSource', default = None, null = True) # from where did we get this recipe up_id = models.IntegerField(null = True, default = None) # id of entry in the source
@@ -544,6 +544,7 @@ class Recipe(models.Model): homepage = models.URLField(blank=True) bugtracker = models.URLField(blank=True) file_path = models.FilePathField(max_length=255) + pathflags = models.CharField(max_length=200, blank=True) def get_layersource_view_url(self): if self.layer_source is None:
@@ -555,18 +556,6 @@ class Recipe(models.Model): def __unicode__(self): return "Recipe " + self.name + ":" + self.version - def get_local_path(self): - if settings.MANAGED and self.layer_version.build is not None and self.layer_version.build.project is not None: - # strip any tag prefixes ('virtual:native:') - layer_path=self.layer_version.layer.local_path.split(":")[-1] - recipe_path=self.file_path.split(":")[-1] - if 0 == recipe_path.find(layer_path): - return recipe_path[len(layer_path)+1:] - else: - return recipe_path - - return self.file_path - def get_vcs_recipe_file_link_url(self): return self.layer_version.get_vcs_file_link_url(self.file_path)
@@ -579,7 +568,8 @@ class Recipe(models.Model): return "" class Meta: - unique_together = ("layer_version", "file_path") + unique_together = (("layer_version", "file_path", "pathflags"), ) + class Recipe_DependencyManager(models.Manager): use_for_related_fields = True
@@ -976,7 +966,6 @@ class Layer(models.Model): up_date = models.DateTimeField(null = True, default = None) name = models.CharField(max_length=100) - local_path = models.FilePathField(max_length=255, null = True, default = None) layer_index_url = models.URLField() vcs_url = GitURLField(default = None, null = True) vcs_web_url = models.URLField(null = True, default = None)
@@ -1009,6 +998,8 @@ class Layer_Version(models.Model): dirpath = models.CharField(max_length=255, null = True, default = None) # LayerBranch.vcs_subdir priority = models.IntegerField(default = 0) # if -1, this is a default layer + local_path = models.FilePathField(max_length=1024, default = "/") # where this layer was checked-out + project = models.ForeignKey('Project', null = True, default = None) # Set if this layer is project-specific; always set for imported layers, and project-set branches # code lifted, with adaptations, from the layerindex-web application https://git.yoctoproject.org/cgit/cgit.cgi/layerindex-web/
lib/toaster/toastergui/templates/bpackage.html +2 -2
@@ -89,7 +89,7 @@ </td> <!-- Layer directory --> {% if not MANAGED or not build.project %} - <td class="recipe__layer_version__layer__local_path">{{package.recipe.layer_version.layer.local_path}}</td> + <td class="recipe__layer_version__local_path">{{package.recipe.layer_version.local_path}}</td> {% endif %} {%else%} <td class="recipe__name"></td>
@@ -97,7 +97,7 @@ <td class="recipe__layer_version__layer__name"></td> <td class="recipe__layer_version__branch"></td> <td class="recipe__layer_version__layer__commit"></td> - <td class="recipe__layer_version__layer__local_path"></td> + <td class="recipe__layer_version__local_path"></td> {%endif%} </tr>
lib/toaster/toastergui/templates/configuration.html +1 -1
@@ -64,7 +64,7 @@ {{lv.commit|truncatechars:13}} </a></td> {% if not MANAGED or not build.project %} - <td>{{lv.layer.local_path}}</td> + <td>{{lv.local_path}}</td> {% endif %} </tr>{% endfor %} </tbody>
lib/toaster/toastergui/templates/package_detail_base.html +1 -1
@@ -141,7 +141,7 @@ Layer directory <i class="icon-question-sign get-help" title="Path to the layer providing the recipe that builds this package"></i> </dt> - <dd><code>{{package.recipe.layer_version.layer.local_path}}</code></dd> + <dd><code>{{package.recipe.layer_version.local_path}}</code></dd> {% endif %} </dl> </div> <!-- row4 well -->
lib/toaster/toastergui/templates/recipe.html +2 -2
@@ -58,13 +58,13 @@ <i class="icon-question-sign get-help" title="Path to the layer providing the recipe"></i> Layer directory </dt> - <dd><code>{{layer.local_path}}</code></dd> + <dd><code>{{object.layer_version.local_path}}</code></dd> {% endif %} <dt> <i class="icon-question-sign get-help" title="Path to the recipe .bb file"></i> Recipe file </dt> - <dd><code>{{object.get_local_path}}</code></dd> + <dd><code>{{object.file_path}} {% if object.pathflags %}<i>({{object.pathflags}})</i>{% endif %}</code></dd> {% if layer_version.branch %} <dt> <i class="icon-question-sign get-help" title="The Git branch of the layer providing the recipe"></i>
lib/toaster/toastergui/templates/recipes.html +2 -2
@@ -84,7 +84,7 @@ {% endwith %} </td> <!-- Recipe file --> - <td class="recipe_file">{{recipe.get_local_path}}</td> + <td class="recipe_file">{{recipe.file_path}} {% if recipe.pathflags %}<i>({{recipe.pathflags}})</i>{% endif %}</td> <!-- Section --> <td class="recipe_section">{{recipe.section}}</td> <!-- License -->
@@ -105,7 +105,7 @@ {% if not MANAGED or not build.project %} <!-- Layer directory --> - <td class="layer_version__layer__local_path">{{recipe.layer_version.layer.local_path}}</td> + <td class="layer_version__local_path">{{recipe.layer_version.local_path}}</td> {% endif %} </tr>
lib/toaster/toastergui/templates/target.html +1 -1
@@ -154,7 +154,7 @@ </td> {% if not MANAGED or not build.project %} <td class="layer_directory"> - {{ package.recipe.layer_version.layer.local_path }} + {{ package.recipe.layer_version.local_path }} </td> {% endif %} </tr>
lib/toaster/toastergui/views.py +11 -11
@@ -725,9 +725,9 @@ eans multiple licenses exist that cover different parts of the source', tc_layerDir = { 'name':'Layer directory', 'qhelp':'Location in disk of the layer providing the recipe that builds the package', - 'orderfield' : _get_toggle_order( request, "recipe__layer_version__layer__local_path" ), - 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__layer__local_path" ), - 'orderkey' : "recipe__layer_version__layer__local_path", + 'orderfield' : _get_toggle_order( request, "recipe__layer_version__local_path" ), + 'ordericon' : _get_toggle_order_icon( request, "recipe__layer_version__local_path" ), + 'orderkey' : "recipe__layer_version__local_path", 'clclass' : 'layer_directory', 'hidden' : 1, }
@@ -1254,10 +1254,10 @@ def recipes(request, build_id): { 'name':'Layer directory', 'qhelp':'Path to the layer prodiving the recipe', - 'orderfield': _get_toggle_order(request, "layer_version__layer__local_path"), - 'ordericon':_get_toggle_order_icon(request, "layer_version__layer__local_path"), - 'orderkey' : 'layer_version__layer__local_path', - 'clclass': 'layer_version__layer__local_path', 'hidden': 1, + 'orderfield': _get_toggle_order(request, "layer_version__local_path"), + 'ordericon':_get_toggle_order_icon(request, "layer_version__local_path"), + 'orderkey' : 'layer_version__local_path', + 'clclass': 'layer_version__local_path', 'hidden': 1, })
@@ -1464,10 +1464,10 @@ def bpackage(request, build_id): tc_layerDir = { 'name':'Layer directory', 'qhelp':'Path to the layer providing the recipe that builds the package', - 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__local_path"), - 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__local_path"), - 'orderkey' : 'recipe__layer_version__layer__local_path', - 'clclass': 'recipe__layer_version__layer__local_path', 'hidden': 1, + 'orderfield': _get_toggle_order(request, "recipe__layer_version__local_path"), + 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__local_path"), + 'orderkey' : 'recipe__layer_version__local_path', + 'clclass': 'recipe__layer_version__local_path', 'hidden': 1, } context['tablecols'].append(tc_layerDir)
Signature · 1 line
1.9.1
PATCH 4/22 toaster logger: assign projects to all builds
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
Bitbake logger now sets a default project if the TOASTER_BRBE or TOASTER_PROJECT Bitbake variables are not set.
This a necessary step in getting all builds under a project, as to unify the MANAGED and interactive modes.
Other small fixes are included, related to the size of the fields in the database.
--- lib/bb/ui/buildinfohelper.py | 37 ++- ...ect__chg_field_project_bitbake_version__chg_.py | 368 +++++++++++++++++++++ lib/toaster/orm/models.py | 15 +- lib/toaster/orm/views.py | 2 +- 4 files changed, 405 insertions(+), 17 deletions(-) create mode 100644 lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py
lib/bb/ui/buildinfohelper.py +27 -10
@@ -31,6 +31,9 @@ from toaster.orm.models import Variable, VariableHistory from toaster.orm.models import Package, Package_File, Target_Installed_Package, Target_File from toaster.orm.models import Task_Dependency, Package_Dependency from toaster.orm.models import Recipe_Dependency + +from toaster.orm.models import Project + from bb.msg import BBLogFormatter as format from django.db import models from pprint import pformat
@@ -103,7 +106,7 @@ class ORMWrapper(object): return vars(self)[dictname][key] - def create_build_object(self, build_info, brbe): + def create_build_object(self, build_info, brbe, project_id): assert 'machine' in build_info assert 'distro' in build_info assert 'distro_version' in build_info
@@ -112,7 +115,26 @@ class ORMWrapper(object): assert 'build_name' in build_info assert 'bitbake_version' in build_info + prj = None + buildrequest = None + if brbe is not None: # this build was triggered by a request from a user + logger.debug(1, "buildinfohelper: brbe is %s" % brbe) + from bldcontrol.models import BuildEnvironment, BuildRequest + br, be = brbe.split(":") + buildrequest = BuildRequest.objects.get(pk = br) + prj = buildrequest.project + + elif project_id is not None: # this build was triggered by an external system for a specific project + logger.debug(1, "buildinfohelper: project is %s" % prj) + prj = Project.objects.get(pk = project_id) + + else: # this build was triggered by a legacy system, or command line interactive mode + prj, created = Project.objects.get_or_create(pk=0, name="Default Project") + logger.debug(1, "buildinfohelper: project is not specified, defaulting to %s" % prj) + + build = Build.objects.create( + project = prj, machine=build_info['machine'], distro=build_info['distro'], distro_version=build_info['distro_version'],
@@ -123,17 +145,11 @@ class ORMWrapper(object): bitbake_version=build_info['bitbake_version']) logger.debug(1, "buildinfohelper: build is created %s" % build) - if brbe is not None: - logger.debug(1, "buildinfohelper: brbe is %s" % brbe) - from bldcontrol.models import BuildEnvironment, BuildRequest - br, be = brbe.split(":") - buildrequest = BuildRequest.objects.get(pk = br) + if buildrequest is not None: buildrequest.build = build buildrequest.save() - build.project_id = buildrequest.project_id - build.save() return build def create_target_objects(self, target_info):
@@ -638,6 +654,7 @@ class BuildInfoHelper(object): self.has_build_history = has_build_history self.tmp_dir = self.server.runCommand(["getVariable", "TMPDIR"])[0] self.brbe = self.server.runCommand(["getVariable", "TOASTER_BRBE"])[0] + self.project = self.server.runCommand(["getVariable", "TOASTER_PROJECT"])[0] logger.debug(1, "buildinfohelper: Build info helper inited %s" % vars(self))
@@ -804,7 +821,7 @@ class BuildInfoHelper(object): assert '_pkgs' in vars(event) build_information = self._get_build_information() - build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe) + build_obj = self.orm_wrapper.create_build_object(build_information, self.brbe, self.project) self.internal_state['build'] = build_obj
@@ -1233,7 +1250,7 @@ class BuildInfoHelper(object): logger.debug(1, "buildinfohelper: Saving stored event %s " % tempevent) self.store_log_event(tempevent) else: - logger.error("buildinfohelper: Events not saved: %s" % self.internal_state['backlog']) + logger.info("buildinfohelper: All events saved") del self.internal_state['backlog'] log_information = {}
lib/toaster/orm/migrations/0021_auto__chg_field_build_project__chg_field_project_bitbake_version__chg_.py new file +368 -0
369 lines not on this page · raw message
lib/toaster/orm/models.py +9 -6
@@ -81,15 +81,18 @@ class ProjectManager(models.Manager): def create(self, *args, **kwargs): raise Exception("Invalid call to Project.objects.create. Use Project.objects.create_project() to create a project") - def get_or_create(self, *args, **kwargs): + def get_or_create(self, **kwargs): + # allow project creation for default data + if 'pk' in kwargs and kwargs['pk'] == 0: + return super(ProjectManager, self).get_or_create(**kwargs) raise Exception("Invalid call to Project.objects.get_or_create. Use Project.objects.create_project() to create a project") class Project(models.Model): search_allowed_fields = ['name', 'short_description', 'release__name', 'release__branch_name'] name = models.CharField(max_length=100) short_description = models.CharField(max_length=50, blank=True) - bitbake_version = models.ForeignKey('BitbakeVersion') - release = models.ForeignKey("Release") + bitbake_version = models.ForeignKey('BitbakeVersion', null=True) + release = models.ForeignKey("Release", null=True) created = models.DateTimeField(auto_now_add = True) updated = models.DateTimeField(auto_now = True) # This is a horrible hack; since Toaster has no "User" model available when
@@ -223,7 +226,7 @@ class Build(models.Model): search_allowed_fields = ['machine', 'cooker_log_path', "target__target", "target__target_image_file__file_name"] - project = models.ForeignKey(Project, null = True) + project = models.ForeignKey(Project) # must have a project machine = models.CharField(max_length=100) distro = models.CharField(max_length=100) distro_version = models.CharField(max_length=100)
@@ -435,8 +438,8 @@ class Task(models.Model): script_type = models.IntegerField(choices=TASK_CODING, default=CODING_NA) line_number = models.IntegerField(default=0) disk_io = models.IntegerField(null=True) - cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, null=True) - elapsed_time = models.DecimalField(max_digits=6, decimal_places=2, null=True) + cpu_usage = models.DecimalField(max_digits=8, decimal_places=2, null=True) + elapsed_time = models.DecimalField(max_digits=8, decimal_places=2, null=True) sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=SSTATE_NA) message = models.CharField(max_length=240) logfile = models.FilePathField(max_length=255, blank=True)
lib/toaster/orm/views.py +1 -1
@@ -37,7 +37,7 @@ from django.views.decorators.csrf import csrf_exempt def eventfile(request): """ Receives a file by POST, and runs toaster-eventreply on this file """ if request.method != "POST": - return HttpResponseBadRequest("This API only accepts POST requests. Post a file with:\n\ncurl -F eventlog=@bitbake_eventlog.json http[s]://[server-address]/orm/eventfile\n", content_type="text/plain;utf8") + return HttpResponseBadRequest("This API only accepts POST requests. Post a file with:\n\ncurl -F eventlog=@bitbake_eventlog.json %s\n" % request.build_absolute_uri(reverse('eventfile')), content_type="text/plain;utf8") # write temporary file (handle, abstemppath) = tempfile.mkstemp(dir="/tmp/")
Signature · 1 line
1.9.1
PATCH 5/22 toastergui: table header bring back
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This patch brings back the table header, which I mistakenly took out when fixing HTML5 compliance.
--- lib/toaster/toastergui/static/js/table.js | 2 +- lib/toaster/toastergui/templates/toastertable.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
lib/toaster/toastergui/static/js/table.js +1 -1
@@ -163,7 +163,7 @@ function tableInit(ctx){ if (tableChromeDone === true) return; - var tableHeadRow = table.find("thead tr"); + var tableHeadRow = table.find("thead#tableheader"); var editColMenu = $("#table-chrome-"+ctx.tableName).find(".editcol"); tableHeadRow.html("");
lib/toaster/toastergui/templates/toastertable.html +1 -1
@@ -92,7 +92,7 @@ <!-- The actual table --> <table class="table table-bordered table-hover tablesorter" id="{{table_name}}"> - <thead> + <thead id="tableheader"> </thead> <tbody></tbody> </table>
Signature · 1 line
1.9.1
PATCH 6/22 toastergui: show relative paths in configvars view
In reply to Alex DAMIANFrom: Ed Bartosh <[email protected]>
Reworked filtering of config paths.
Stripped clone paths, topdir and its parent directory from the paths to config files in configvars view.
[YOCTO #7463]
--- lib/toaster/toastergui/templates/configvars.html | 10 +-- lib/toaster/toastergui/templatetags/projecttags.py | 76 +++++++--------------- lib/toaster/toastergui/views.py | 19 ++++-- 3 files changed, 42 insertions(+), 63 deletions(-)
lib/toaster/toastergui/templates/configvars.html +6 -4
@@ -54,9 +54,11 @@ <td class="variable_name"><a data-toggle="modal" href="#variable-{{variable.pk}}">{{variable.variable_name}}</a></td> <td class="variable_value"><a data-toggle="modal" href="#variable-{{variable.pk}}">{{variable.variable_value|truncatechars:153}}</a></td> <td class="file"><a data-toggle="modal" href="#variable-{{variable.pk}}"> - {% if variable.vhistory.all %} {% autoescape off %} - {{variable.vhistory.all | filter_setin_files:file_filter | cut_layer_path_prefix:layer_names}} - {% endautoescape %} {% endif %} + {% if variable.vhistory.all %} + {% for path in variable.vhistory.all|filter_setin_files:file_filter %} + {{path|cut_path_prefix:dirstostrip}}<p> + {% endfor %} + {% endif %} </a></td> <td class="description"> {% if variable.description %}
@@ -115,7 +117,7 @@ <tbody> {% for vh in variable.vhistory.all %} <tr> - <td>{{forloop.counter}}</td><td>{{vh.file_name|cut_layer_path_prefix:layer_names}}</td><td>{{vh.operation}}</td><td>{{vh.line_number}}</td> + <td>{{forloop.counter}}</td><td>{{vh.file_name|cut_path_prefix:dirstostrip}}</td><td>{{vh.operation}}</td><td>{{vh.line_number}}</td> </tr> {%endfor%} </tbody>
lib/toaster/toastergui/templatetags/projecttags.py +23 -53
92 lines not on this page · raw message
lib/toaster/toastergui/views.py +13 -6
@@ -28,6 +28,8 @@ from django.shortcuts import render, redirect from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency from orm.models import Target_Installed_Package, Target_File, Target_Image_File, BuildArtifact +from bldcontrol.models import BuildEnvironment, BuildRequest +from bldcontrol import bbcontroller from django.views.decorators.cache import cache_control from django.core.urlresolvers import reverse from django.core.exceptions import MultipleObjectsReturned
@@ -39,6 +41,7 @@ from datetime import timedelta, datetime, date from django.utils import formats from toastergui.templatetags.projecttags import json as jsonfilter import json +from os.path import dirname # all new sessions should come through the landing page; # determine in which mode we are running in, and redirect appropriately
@@ -1300,11 +1303,6 @@ def configvars(request, build_id): variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1)) - layers = Layer.objects.filter(layer_version_layer__projectlayer__project__build=build_id).order_by("-name") - layer_names = map(lambda layer : layer.name, layers) - # special case for meta built-in layer - layer_names.append('meta') - # show all matching files (not just the last one) file_filter= search_term + ":" if filter_string.find('/conf/') > 0:
@@ -1317,6 +1315,15 @@ def configvars(request, build_id): file_filter += '/bitbake.conf' build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path) + clones = [] + for breq in BuildRequest.objects.filter(build_id=build_id): + bc = bbcontroller.getBuildEnvironmentController(pk = breq.environment.id) + for brl in breq.brlayer_set.all(): + localdirname = bc.getGitCloneDirectory(brl.giturl, brl.commit) + if not localdirname.startswith("/"): + localdirname = os.path.join(bc.be.sourcedir, localdirname) + clones.append(localdirname) + context = { 'objectname': 'configvars', 'object_search_display':'BitBake variables',
@@ -1327,7 +1334,7 @@ def configvars(request, build_id): 'total_count':queryset_with_search.count(), 'default_orderby' : 'variable_name:+', 'search_term':search_term, - 'layer_names' : layer_names, + 'dirstostrip': clones + [dirname(build_dir), dirname(dirname(build_dir))], # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns 'tablecols' : [ {'name': 'Variable',
Signature · 1 line
1.9.1
PATCH 7/22 toaster: Fix toastertable header
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
Fixes regression introduced by 'tables updates for HTML5 compliance'
--- lib/toaster/toastergui/templates/toastertable.html | 1 + 1 file changed, 1 insertion(+)
lib/toaster/toastergui/templates/toastertable.html +1 -0
@@ -93,6 +93,7 @@ <!-- The actual table --> <table class="table table-bordered table-hover tablesorter" id="{{table_name}}"> <thead id="tableheader"> + <tr><th></th></tr> </thead> <tbody></tbody> </table>
Signature · 1 line
1.9.1
PATCH 8/22 toaster: Add a simplified ToasterTable template
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
This is like the normal ToasterTable template but with a number of features removed such as edit columns and has a lower profile compared to the "main" tables.
--- .../toastergui/templates/toastertable-simple.html | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 lib/toaster/toastergui/templates/toastertable-simple.html
lib/toaster/toastergui/templates/toastertable-simple.html new file +112 -0
@@ -0,0 +1,112 @@ + +{% load static %} +{% load projecttags %} + +<script src="{% static 'js/table.js' %}"></script> +<script src="{% static 'js/layerBtn.js' %}"></script> +<script> + $(document).ready(function() { + (function(){ + + var ctx = { + tableName : "{{table_name}}", + url : "{{ xhr_table_url }}", + title : "{{title}}", + projectLayers : {{projectlayers|json}}, + }; + + try { + tableInit(ctx); + } catch (e) { + document.write("Problem loading table widget: " + e); + } + })(); + }); +</script> + +<!-- filter modal --> +<div id="filter-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false"> + <form id="filter-modal-form" style="margin-bottom: 0px"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + <h3 id="filter-modal-title"> </h3> + </div> + <div class="modal-body"> + <p>Show:</p> + <span id="filter-actions"></span> + </div> + <div class="modal-footer"> + <button class="btn btn-primary" type="submit">Apply</button> + </div> + </form> +</div> +<button id="clear-filter-btn" style="display:none"></button> + +<div class="row-fluid" id="no-results-{{table_name}}" style="display:none"> + <div class="alert"> + <form class="no-results input-append"> + <input class="input-xlarge" id="new-search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{{request.GET.search}}"/> + <a href="#" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1"> + <i class="icon-remove"></i> + </a> + <button class="btn search-submit-{{table_name}}" >Search</button> + <button class="btn btn-link remove-search-btn-{{table_name}}">Show {{title|lower}} + </button> + </form> + </div> +</div> + + +<div id="table-container-{{table_name}}"> + <!-- control header --> + <div class="row-fluid" id="table-chrome-{{table_name}}"> + <div class="navbar-search input-append pull-left"> + + <input class="input-xlarge" id="search-input-{{table_name}}" name="search" type="text" placeholder="Search {{title|lower}}" value="{{request.GET.search}}"/> + <a href="#" style="display:none" class="add-on btn remove-search-btn-{{table_name}}" tabindex="-1"> + <i class="icon-remove"></i> + </a> + <button class="btn" id="search-submit-{{table_name}}" >Search</button> + </div> + + <div class="pull-right"> + + <div style="display:inline"> + <span class="divider-vertical"></span> + <span class="help-inline" style="padding-top:5px;">Show rows:</span> + <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}"> + {% with "10 25 50 100 150" as list%} + {% for i in list.split %} + <option value="{{i}}">{{i}}</option> + {% endfor %} + {% endwith %} + </select> + </div> + </div> + </div> + + <!-- The actual table --> + <table class="table table-bordered table-hover tablesorter" id="{{table_name}}"> + <thead> + <tr><th></th></tr> + </thead> + <tbody></tbody> + </table> + + <!-- Pagination controls --> + <div class="pagination pagination-centered"> + <ul id="pagination-{{table_name}}" class="pagination" style="display: block-inline"> + </ul> + + <div class="pull-right"> + <span class="help-inline" style="padding-top:5px;">Show rows:</span> + <select style="margin-top:5px;margin-bottom:0px;" class="pagesize-{{table_name}}"> + {% with "10 25 50 100 150" as list%} + {% for i in list.split %} + <option value="{{i}}">{{i}}</option> + {% endfor %} + {% endwith %} + </select> + </div> + </div> +</div>
Signature · 1 line
1.9.1
PATCH 9/22 toaster: Port layerdetails to ToasterTables
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
This ports the layerdetails page to using ToasterTables Also some whitespace and strict clean ups in the existing layerdetails js and html template.
--- lib/toaster/toastergui/static/js/layerdetails.js | 70 ++- lib/toaster/toastergui/static/js/libtoaster.js | 3 + lib/toaster/toastergui/static/js/table.js | 77 ++- lib/toaster/toastergui/tables.py | 87 ++- lib/toaster/toastergui/templates/layerdetails.html | 594 +++++++-------------- .../toastergui/templates/toastertable-filter.html | 18 + .../toastergui/templates/toastertable-simple.html | 26 +- lib/toaster/toastergui/templates/toastertable.html | 50 +- lib/toaster/toastergui/urls.py | 6 +- lib/toaster/toastergui/views.py | 49 -- lib/toaster/toastergui/widgets.py | 17 +- 11 files changed, 415 insertions(+), 582 deletions(-) create mode 100644 lib/toaster/toastergui/templates/toastertable-filter.html
lib/toaster/toastergui/static/js/layerdetails.js +52 -18
@@ -1,4 +1,4 @@ -"use strict" +"use strict"; function layerDetailsPageInit (ctx) {
@@ -54,7 +54,7 @@ function layerDetailsPageInit (ctx) { /* Add dependency layer button click handler */ layerDepBtn.click(function(){ - if (currentLayerDepSelection == undefined) + if (currentLayerDepSelection === undefined) return; addRemoveDep(currentLayerDepSelection.id, true, function(){
@@ -122,13 +122,6 @@ function layerDetailsPageInit (ctx) { }); }); - $(".build-target-btn").click(function(){ - /* fire a build */ - var target = $(this).data('target-name'); - libtoaster.startABuild(ctx.projectBuildUrl, libtoaster.ctx.projectId, target, null, null); - window.location.replace(libtoaster.ctx.projectPageUrl); - }); - function defaultAddBtnText(){ var text = " Add the "+ctx.layerVersion.name+" layer to your project"; addRmLayerBtn.text(text);
@@ -155,9 +148,48 @@ function layerDetailsPageInit (ctx) { } } - window.location.hash = "targets"; + window.location.hash = "recipes"; } + $("#recipestable").on('table-done', function(e, total, tableParams){ + ctx.numTargets = total; + + if (total === 0 && !tableParams.search) { + $("#no-recipes-yet").show(); + } else { + $("#no-recipes-yet").hide(); + } + + $("#targets-tab").removeClass("muted"); + if (window.location.hash === "#recipes"){ + /* re run the machinesTabShow to update the text */ + targetsTabShow(); + } + + $(".build-target-btn").unbind('click'); + $(".build-target-btn").click(function(){ + /* fire a build */ + var target = $(this).data('target-name'); + libtoaster.startABuild(ctx.projectBuildUrl, libtoaster.ctx.projectId, target, null, null); + window.location.replace(libtoaster.ctx.projectPageUrl); + }); + }); + + $("#machinestable").on('table-done', function(e, total, tableParams){ + ctx.numMachines = total; + + if (total === 0 && !tableParams.search) + $("#no-machines-yet").show(); + else + $("#no-machines-yet").hide(); + + $("#machines-tab").removeClass("muted"); + if (window.location.hash === "#machines"){ + /* re run the machinesTabShow to update the text */ + machinesTabShow(); + } + }); + $("#targets-tab").on('show', targetsTabShow); function machinesTabShow(){
@@ -187,7 +219,7 @@ function layerDetailsPageInit (ctx) { /* Enables the Build target and Select Machine buttons and switches the * add/remove button */ - function setLayerInCurrentPrj(added, depsList) { + function setLayerInCurrentPrj(added) { ctx.layerVersion.inCurrentPrj = added; if (added){
@@ -224,18 +256,20 @@ function layerDetailsPageInit (ctx) { } } - $("#dismiss-alert").click(function(){ $(this).parent().hide() }); + $("#dismiss-alert").click(function(){ + $(this).parent().fadeOut(); + }); /* Add or remove this layer from the project */ addRmLayerBtn.click(function() { - var add = ($(this).data('directive') === "add") + var add = ($(this).data('directive') === "add"); libtoaster.addRmLayer(ctx.layerVersion, add, function (layersList){ var alertMsg = $("#alert-msg"); alertMsg.html(libtoaster.makeLayerAddRmAlertMsg(ctx.layerVersion, layersList, add)); - setLayerInCurrentPrj(add, layersList); + setLayerInCurrentPrj(add); $("#alert-area").show(); });
@@ -250,9 +284,9 @@ function layerDetailsPageInit (ctx) { * from. */ var entryElement = mParent.find("input"); - if (entryElement.length == 0) + if (entryElement.length === 0) entryElement = mParent.find("textarea"); - if (entryElement.length == 0) { + if (entryElement.length === 0) { console.warn("Could not find element to get data from for this change"); return; }
@@ -293,7 +327,7 @@ function layerDetailsPageInit (ctx) { /* Disable the change button when we have no data in the input */ $("dl input, dl textarea").on("input",function() { - if ($(this).val().length == 0) + if ($(this).val().length === 0) $(this).parent().children(".change-btn").attr("disabled", "disabled"); else $(this).parent().children(".change-btn").removeAttr("disabled");
@@ -322,7 +356,7 @@ function layerDetailsPageInit (ctx) { }); /* Hide the right column if it contains no information */ - if ($("dl.item-info").children(':visible').length == 0) { + if ($("dl.item-info").children(':visible').length === 0) { $("dl.item-info").parent().hide(); }
lib/toaster/toastergui/static/js/libtoaster.js +3 -0
@@ -216,6 +216,9 @@ var libtoaster = (function (){ str += "&"; } + /* Maintain the current hash */ + str += window.location.hash; + return str; }
lib/toaster/toastergui/static/js/table.js +48 -29
148 lines not on this page · raw message
lib/toaster/toastergui/tables.py +78 -9
112 lines not on this page · raw message
lib/toaster/toastergui/templates/layerdetails.html +183 -411
633 lines not on this page · raw message
lib/toaster/toastergui/templates/toastertable-filter.html new file +18 -0
@@ -0,0 +1,18 @@ +<!-- filter modal --> +<div id="filter-modal-{{table_name}}" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false"> + <form id="filter-modal-form-{{table_name}}" style="margin-bottom: 0px"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> + <h3 id="filter-modal-title-{{table_name}}"> </h3> + </div> + <div class="modal-body"> + <p>Show:</p> + <span id="filter-actions-{{table_name}}"></span> + </div> + <div class="modal-footer"> + <button class="btn btn-primary" type="submit">Apply</button> + </div> + </form> +</div> +<button id="clear-filter-btn-{{table_name}}" style="display:none"></button> +<!-- end filter modal -->
lib/toaster/toastergui/templates/toastertable-simple.html +4 -22
@@ -24,23 +24,7 @@ }); </script> -<!-- filter modal --> -<div id="filter-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="false"> - <form id="filter-modal-form" style="margin-bottom: 0px"> - <div class="modal-header"> - <button type="button" class="close" data-dismiss="modal" aria-hidden="true">x</button> - <h3 id="filter-modal-title"> </h3> - </div> - <div class="modal-body"> - <p>Show:</p> - <span id="filter-actions"></span> - </div> - <div class="modal-footer"> - <button class="btn btn-primary" type="submit">Apply</button> - </div> - </form> -</div> -<button id="clear-filter-btn" style="display:none"></button> +{% include 'toastertable-filter.html' %} <div class="row-fluid" id="no-results-{{table_name}}" style="display:none"> <div class="alert">
@@ -55,9 +39,7 @@ </form> </div> </div> - - -<div id="table-container-{{table_name}}"> +<div id="table-container-{{table_name}}" style="visibility: hidden"> <!-- control header --> <div class="row-fluid" id="table-chrome-{{table_name}}"> <div class="navbar-search input-append pull-left">
@@ -94,8 +76,8 @@ </table> <!-- Pagination controls --> - <div class="pagination pagination-centered"> - <ul id="pagination-{{table_name}}" class="pagination" style="display: block-inline"> + <div class="pagination pagination-centered" id="pagination-{{table_name}}"> + <ul class="pagination" style="display: block-inline"> </ul> <div class="pull-right">
lib/toaster/toastergui/templates/toastertable.html +18 -32
75 lines not on this page · raw message
lib/toaster/toastergui/urls.py +4 -2
@@ -83,7 +83,9 @@ urlpatterns = patterns('toastergui.views', url(r'^project/(?P<pid>\d+)/configuration$', 'projectconf', name='projectconf'), url(r'^project/(?P<pid>\d+)/builds/$', 'projectbuilds', name='projectbuilds'), - url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)$', 'layerdetails', name='layerdetails'), + url(r'^project/(?P<pid>\d+)/layer/(?P<layerid>\d+)$', + ToasterTemplateView.as_view(template_name='layerdetails.html'), + name='layerdetails'), url(r'^project/(?P<pid>\d+)/layer/$', lambda x,pid: HttpResponseBadRequest(), name='base_layerdetails'), # the import layer is a project-specific functionality;
@@ -117,7 +119,7 @@ urlpatterns = patterns('toastergui.views', url(r'^xhr_datatypeahead/(?P<pid>\d+)$', 'xhr_datatypeahead', name='xhr_datatypeahead'), url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'), url(r'^xhr_updatelayer/$', 'xhr_updatelayer', name='xhr_updatelayer'), - url(r'^xhr_tables/(?P<pid>\d+)/', include('toastergui.tables')), + url(r'^xhr_tables/project/(?P<pid>\d+)/', include('toastergui.tables')), # dashboard for failed build requests url(r'^project/(?P<pid>\d+)/buildrequest/(?P<brid>\d+)$', 'buildrequestdetails', name='buildrequestdetails'),
lib/toaster/toastergui/views.py +0 -49
61 lines not on this page · raw message
lib/toaster/toastergui/widgets.py +7 -10
@@ -26,7 +26,7 @@ from django.core import serializers from django.core.cache import cache from django.core.paginator import Paginator, EmptyPage from django.db.models import Q -from orm.models import Project, ProjectLayer +from orm.models import Project, ProjectLayer, Layer_Version from django.template import Context, Template from django.core.serializers.json import DjangoJSONEncoder from django.core.exceptions import FieldError
@@ -43,9 +43,13 @@ class ToasterTemplateView(TemplateView): def get_context_data(self, **kwargs): context = super(ToasterTemplateView, self).get_context_data(**kwargs) if 'pid' in kwargs: - context['project'] = Project.objects.get(pk=kwargs['pid']) + context['project'] = Project.objects.get(pk=kwargs['pid']) + + context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project'])) + + if 'layerid' in kwargs: + context['layerversion'] = Layer_Version.objects.get(pk=kwargs['layerid']) - context['projectlayers'] = map(lambda prjlayer: prjlayer.layercommit.id, ProjectLayer.objects.filter(project=context['project'])) return context
@@ -269,13 +273,6 @@ class ToasterTable(View): 'rows' : [], } - # Flatten all the fields we will need into one list - fields = [] - for col in self.columns: - if type(col['field_name']) is list: - fields.extend(col['field_name']) - else: - fields.append(col['field_name']) try: for row in page.object_list:
Signature · 1 line
1.9.1
PATCH 10/22 toaster: Add an invalidate cache method on data update signal
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
Instead of relying on the cache expiring over an amount of time we can listen to the changed notification on the models to invalidate the cache. Also fixes overlapping cache names.
--- lib/toaster/orm/models.py | 12 ++++++++++++ lib/toaster/toastergui/tables.py | 5 ----- lib/toaster/toastergui/widgets.py | 6 ++++-- 3 files changed, 16 insertions(+), 7 deletions(-)
lib/toaster/orm/models.py +12 -0
@@ -26,6 +26,7 @@ from django.utils import timezone from django.core import validators from django.conf import settings +import django.db.models.signals class GitURLValidator(validators.URLValidator): import re
@@ -1176,3 +1177,14 @@ class LogMessage(models.Model): message=models.CharField(max_length=240) pathname = models.FilePathField(max_length=255, blank=True) lineno = models.IntegerField(null=True) + +def invalidate_cache(**kwargs): + from django.core.cache import cache + try: + cache.clear() + except Exception as e: + print "Problem with cache backend: Failed to clear cache" + pass + +django.db.models.signals.post_save.connect(invalidate_cache) +django.db.models.signals.post_delete.connect(invalidate_cache)
lib/toaster/toastergui/tables.py +0 -5
@@ -323,11 +323,6 @@ class LayerRecipesTable(RecipesTable): - - - - - # This needs to be staticaly defined here as django reads the url patterns # on start up urlpatterns = (
lib/toaster/toastergui/widgets.py +4 -2
@@ -245,6 +245,9 @@ class ToasterTable(View): for key, val in request.GET.iteritems(): cache_name = cache_name + str(key) + str(val) + for key, val in kwargs.iteritems(): + cache_name = cache_name + str(key) + str(val) + data = cache.get(cache_name) if data:
@@ -306,8 +309,7 @@ class ToasterTable(View): except FieldError: print "Error: Requested field does not exist" - data = json.dumps(data, indent=2, cls=DjangoJSONEncoder) - cache.set(cache_name, data, 10) + cache.set(cache_name, data, 60*30) return data
Signature · 1 line
1.9.1
PATCH 11/22 toaster-gui: Show task name for the builds in /builds
In reply to Alex DAMIANFrom: Ed Bartosh <[email protected]>
Task name was only shown for the latest builds in project view. Now it should be shown also in builds view.
--- lib/toaster/toastergui/templates/brtargets.html | 20 ++++++++++++++++++++ .../toastergui/templates/managed_mrb_section.html | 12 +++++------- 2 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 lib/toaster/toastergui/templates/brtargets.html
lib/toaster/toastergui/templates/brtargets.html new file +20 -0
@@ -0,0 +1,20 @@ +<span data-toggle="tooltip" + {% if buildrequest.brtarget_set.all.count > 1 %} + title="Targets: + {% for target in buildrequest.brtarget_set.all %} + {% if target.task %} + {{target.target}}:{{target.task}} + {% else %} + {{target.target}} + {% endif %} + {%endfor%}" + {%endif%}> + {% if buildrequest.brtarget_set.all.0.task %} + {{buildrequest.brtarget_set.all.0.target}}:{{buildrequest.brtarget_set.all.0.task}} + {% else %} + {{buildrequest.brtarget_set.all.0.target}} + {% endif %} + {% if buildrequest.brtarget_set.all.count > 1 %} + (+ {{buildrequest.brtarget_set.all.count|add:"-1"}}) + {% endif %} +</span>
lib/toaster/toastergui/templates/managed_mrb_section.html +5 -7
@@ -26,8 +26,7 @@ {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} <a href="{%url 'builddashboard' build.pk%}" class="{%if build.outcome == build.SUCCEEDED %}success{%else%}error{%endif%}"> {% endif %} - <span data-toggle="tooltip" {%if build.target_set.all.count > 1%}title="Targets: {%for target in build.target_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{build.target_set.all.0.target}} {%if build.target_set.all.count > 1%}(+ {{build.target_set.all.count|add:"-1"}}){%endif%} - </span> + {% include "brtargets.html" %} {%if build.outcome == build.SUCCEEDED or build.outcome == build.FAILED %} </a> {% endif %}
@@ -84,7 +83,7 @@ {% if buildrequest.state == buildrequest.REQ_FAILED %} <div class="span3 lead"> <a href="{%url 'buildrequestdetails' buildrequest.project.id buildrequest.pk%}" class="error"> - <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> + {% include "brtargets.html" %} </a> </div> <div class="span2 lead">
@@ -113,8 +112,7 @@ {% elif buildrequest.state == buildrequest.REQ_QUEUED %} <div class="lead span5"> - - <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> + {% include "brtargets.html" %} </div> <div class="span4 lead">Build queued <i title="This build will start as soon as a build server is available" class="icon-question-sign get-help get-help-blue heading-help" data-toggle="tooltip"></i>
@@ -124,7 +122,7 @@ {% elif buildrequest.state == buildrequest.REQ_CREATED %} <div class="lead span3"> - <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> + {% include "brtargets.html" %} </div> <div class="span6" > <span class="lead">Creating build</span>
@@ -133,7 +131,7 @@ {% elif buildrequest.state == buildrequest.REQ_INPROGRESS %} <div class="lead span5"> - <span data-toggle="tooltip" {%if buildrequest.brtarget_set.all.count > 1%}title="Targets: {%for target in buildrequest.brtarget_set.all%}{{target.target}} {%endfor%}"{%endif%}>{{buildrequest.brtarget_set.all.0.target}} {%if buildrequest.brtarget_set.all.count > 1%}(+ {{buildrequest.brtarget_set.all.count|add:"-1"}}){%endif%} </span> + {% include "brtargets.html" %} </div> <div class="span4 lead"> Checking out layers
Signature · 1 line
1.9.1
PATCH 12/22 toastergui: Consider task name when restarting a build in /builds
In reply to Alex DAMIANFrom: Ed Bartosh <[email protected]>
Previously the same issue was fixed for project view.
'Run again' button now restarts builds using target:task also in builds view.
[YOCTO #7442]
--- .../toastergui/templates/managed_mrb_section.html | 16 ++++++++++++++-- lib/toaster/toastergui/templates/runagain.html | 7 +++++++ lib/toaster/toastergui/templatetags/projecttags.py | 7 ++++--- 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 lib/toaster/toastergui/templates/runagain.html
lib/toaster/toastergui/templates/managed_mrb_section.html +14 -2
@@ -54,7 +54,17 @@ Build time: <a href="{% url 'buildtime' build.pk %}">{{ build.timespent|sectohms }}</a> </span> {% if build.project %} - <button class="btn {%if build.outcome == build.SUCCEEDED%}btn-success{%elif build.outcome == build.FAILED%}btn-danger{%else%}btn-info{%endif%} pull-right" onclick='scheduleBuild({% url 'xhr_projectbuild' build.project.id as bpi%}{{bpi|json}}, {{build.project.name|json}}, {% url 'project' buildrequest.project.id as bpurl%}{{bpurl|json}}, {{build.get_sorted_target_list|mapselect:'target'|json}})'>Run again</button> + <button class="btn + {% if build.outcome == build.SUCCEEDED %} + btn-success + {% elif build.outcome == build.FAILED %} + btn-danger + {% else %} + btn-info + {%endif%} + pull-right" + {% include "runagain.html" %} + </button> {% endif %} </div> {%endif%}
@@ -104,8 +114,10 @@ <span class="lead{%if not MANAGED or not buildrequest.project%} pull-right{%endif%}"> Build time: {{ buildrequest.get_duration|sectohms }} </span> - <button class="btn btn-danger pull-right" onclick='scheduleBuild({% url 'xhr_projectbuild' buildrequest.project.id as bpi%}{{bpi|json}}, {{buildrequest.project.name|json}}, {% url 'project' buildrequest.project.id as bpurl%}{{bpurl|json}}, {{buildrequest.get_sorted_target_list|mapselect:'target'|json}})'>Run again</button> + <button class="btn btn-danger pull-right" + {% include "runagain.html" %} + </button> </div>
lib/toaster/toastergui/templates/runagain.html new file +7 -0
@@ -0,0 +1,7 @@ +{% load projecttags %} +onclick='scheduleBuild( + {% url 'xhr_projectbuild' buildrequest.project.id as bpi %}{{bpi|json}}, + {{buildrequest.project.name|json}}, + {% url 'project' buildrequest.project.id as bpurl %}{{bpurl|json}}, + {{buildrequest.brtarget_set.all|get_tasks|json}}) +'>Run again
lib/toaster/toastergui/templatetags/projecttags.py +4 -3
@@ -44,9 +44,10 @@ def sectohms(time): return "%02d:%02d:%02d" % (hours, int((tdsec - (hours * 3600))/ 60), int(tdsec) % 60) [email protected](name = 'mapselect') -def mapselect(value, argument): - return map(lambda x: vars(x)[argument], value) [email protected](name = 'get_tasks') +def get_tasks(queryset): + return list(target + ':' + task if task else target \ + for target, task in queryset.values_list('target', 'task')) @register.filter(name = "json")
Signature · 1 line
1.9.1
PATCH 13/22 toaster: refactor checksettings command
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This patch refactors the checksetting command to prevent early return from the handle function.
It also adds a check that marks IN PROGRESS builds at startup time as FAILED. Minor changes to BuildRequest and Build classes ensure useful string representation for the objects.
--- .../management/commands/checksettings.py | 26 ++++++++++++++++++++-- lib/toaster/bldcontrol/models.py | 3 +++ lib/toaster/orm/models.py | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-)
lib/toaster/bldcontrol/management/commands/checksettings.py +24 -2
@@ -2,7 +2,7 @@ from django.core.management.base import NoArgsCommand, CommandError from django.db import transaction from bldcontrol.bbcontroller import getBuildEnvironmentController, ShellCmdException from bldcontrol.models import BuildRequest, BuildEnvironment, BRError -from orm.models import ToasterSetting +from orm.models import ToasterSetting, Build import os def DN(path):
@@ -61,7 +61,7 @@ class Command(NoArgsCommand): return DN(self._find_first_path_for_file(DN(self.guesspath), "bblayers.conf", 4)) - def handle(self, **options): + def _verify_artifact_storage_dir(self): # verify that we have a settings for downloading artifacts while ToasterSetting.objects.filter(name="ARTIFACTS_STORAGE_DIR").count() == 0: guessedpath = os.getcwd() + "/toaster_build_artifacts/"
@@ -78,7 +78,10 @@ class Command(NoArgsCommand): else: raise ose ToasterSetting.objects.create(name="ARTIFACTS_STORAGE_DIR", value=artifacts_storage_dir) + return 0 + + def _verify_build_environment(self): self.guesspath = DN(DN(DN(DN(DN(DN(DN(__file__))))))) # refuse to start if we have no build environments while BuildEnvironment.objects.count() == 0:
@@ -197,12 +200,16 @@ class Command(NoArgsCommand): while (_verify_be()): pass + return 0 + def _verify_default_settings(self): # verify that default settings are there if ToasterSetting.objects.filter(name = 'DEFAULT_RELEASE').count() != 1: ToasterSetting.objects.filter(name = 'DEFAULT_RELEASE').delete() ToasterSetting.objects.get_or_create(name = 'DEFAULT_RELEASE', value = '') + return 0 + def _verify_builds_in_progress(self): # we are just starting up. we must not have any builds in progress, or build environments taken for b in BuildRequest.objects.filter(state = BuildRequest.REQ_INPROGRESS): BRError.objects.create(req = b, errtype = "toaster", errmsg = "Toaster found this build IN PROGRESS while Toaster started up. This is an inconsistent state, and the build was marked as failed")
@@ -211,4 +218,19 @@ class Command(NoArgsCommand): BuildEnvironment.objects.update(lock = BuildEnvironment.LOCK_FREE) + # also mark "In Progress builds as failures" + from django.utils import timezone + Build.objects.filter(outcome = Build.IN_PROGRESS).update(outcome = Build.FAILED, completed_on = timezone.now()) + return 0 + + + + def handle(self, **options): + retval = 0 + retval += self._verify_artifact_storage_dir() + retval += self._verify_build_environment() + retval += self._verify_default_settings() + retval += self._verify_builds_in_progress() + + return retval
lib/toaster/bldcontrol/models.py +3 -0
@@ -125,6 +125,9 @@ class BuildRequest(models.Model): def get_machine(self): return self.brvariable_set.get(name="MACHINE").value + def __str__(self): + return "%s %s" % (self.project, self.get_state_display()) + # These tables specify the settings for running an actual build. # They MUST be kept in sync with the tables in orm.models.Project*
lib/toaster/orm/models.py +3 -0
@@ -267,6 +267,9 @@ class Build(models.Model): def toaster_exceptions(self): return self.logmessage_set.filter(level=LogMessage.EXCEPTION) + def __str__(self): + return "%d %s %s" % (self.id, self.project, ",".join([t.target for t in self.target_set.all()])) + # an Artifact is anything that results from a Build, and may be of interest to the user, and is not stored elsewhere class BuildArtifact(models.Model):
Signature · 1 line
1.9.1
PATCH 14/22 toasterui: replace cookie-based preferences with session data
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
We switch from storing the user preferences using cookies to saving them in the server-side session.
Patch for "count/pagesize" and "orderby" fields in the table-based pages.
This patch will solve two problems:
* the browser-side race between the GET header data and the cookie data
* page breakages when field names chances in orderby statements.
--- .../toastergui/templates/basetable_bottom.html | 4 --- .../toastergui/templates/basetable_top.html | 9 +----- .../templates/detail_pagination_bottom.html | 5 --- .../toastergui/templates/detail_sorted_header.html | 2 +- lib/toaster/toastergui/views.py | 37 +++++++++++----------- 5 files changed, 20 insertions(+), 37 deletions(-)
lib/toaster/toastergui/templates/basetable_bottom.html +0 -4
@@ -60,8 +60,6 @@ // load cookie for number of entries to be displayed on page if ({{request.GET.count}} != "") { pagesize = {{request.GET.count}}; - } else { - pagesize = $.cookie('_count'); } $('.pagesize option').prop('selected', false)
@@ -84,8 +82,6 @@ $('.progress, .lead span').tooltip({container:'table', placement:'top'}); $(".pagesize").change(function () { - // save cookie with pagesize - $.cookie("_count", $(this).val(), { path : $(location).attr('pathname') }); reload_params({"count":$(this).val()}); }); });
lib/toaster/toastergui/templates/basetable_top.html +1 -8
@@ -156,13 +156,6 @@ showhideImmediateTableAction( clname, sh, orderkey ); } - // - // saves a cookie with selected order field - // - function saveOrderCookie( orderfield ) { - $.cookie("orderby", orderfield, { path: $(location).attr('pathname') }); - } - </script> <!-- control header -->
@@ -230,7 +223,7 @@ <tr> {% for tc in tablecols %}<th class="{{tc.dclass}} {{tc.clclass}}"> {%if tc.qhelp%}<i class="icon-question-sign get-help" title="{{tc.qhelp}}"></i>{%endif%} - {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })" onclick="saveOrderCookie('{{tc.orderfield}}')">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%} + {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%} {%if tc.ordericon%} <i class="icon-caret-{{tc.ordericon}}"></i>{%endif%} {%if tc.filter%}<div class="btn-group pull-right"> <a href="#filter_{{tc.filter.class}}" role="button" class="btn btn-mini {%if request.GET.filter%}{{tc.filter.options|filtered_icon:request.GET.filter}} {%endif%}" {%if request.GET.filter and tc.filter.options|filtered_tooltip:request.GET.filter %} title="<p>{{tc.filter.options|filtered_tooltip:request.GET.filter}}</p><p><a class='btn btn-small btn-primary' href=javascript:reload_params({'filter':''})>Show all {% if filter_search_display %}{{filter_search_display}}{% else %}{{objectname}}{% endif %}</a></p>" {%endif%} data-toggle="modal"> <i class="icon-filter filtered"></i> </a>
lib/toaster/toastergui/templates/detail_pagination_bottom.html +0 -5
@@ -41,17 +41,12 @@ // load cookie for number of entries to be displayed on page if ({{request.GET.count}} != "") { pagesize = {{request.GET.count}}; - } else { - pagesize = $.cookie('_count'); } - $('.pagesize option').prop('selected', false) .filter('[value="' + pagesize + '"]') .attr('selected', true); $(".pagesize").change(function () { - // save cookie with pagesize - $.cookie("_count", $(this).val(), { path : $(location).attr('pathname') }); reload_params({"count":$(this).val()}); }); });
lib/toaster/toastergui/templates/detail_sorted_header.html +1 -1
@@ -11,7 +11,7 @@ <tr> {% for tc in tablecols %}<th class="{{tc.dclass}} {{tc.clclass}}"> {%if tc.qhelp%}<i class="icon-question-sign get-help" title="{{tc.qhelp}}"></i>{%endif%} - {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })" onclick="saveOrderCookie('{{tc.orderfield}}')">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%} + {%if tc.orderfield%}<a {%if tc.ordericon%} class="sorted" {%endif%}href="javascript:reload_params({'page': 1, 'orderby' : '{{tc.orderfield}}' })">{{tc.name}}</a>{%else%}<span class="muted">{{tc.name}}</span>{%endif%} {%if tc.ordericon%} <i class="icon-caret-{{tc.ordericon}}"></i>{%endif%} {% if request.GET.search and forloop.first %} <span class="badge badge-info">{{objects.paginator.count}}</span>
lib/toaster/toastergui/views.py +18 -19
@@ -20,7 +20,6 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import operator,re -import HTMLParser from django.db.models import Q, Sum, Count, Max from django.db import IntegrityError
@@ -278,11 +277,11 @@ def _get_parameters_values(request, default_count, default_order): # set cookies for parameters. this is usefull in case parameters are set # manually from the GET values of the link -def _save_parameters_cookies(response, pagesize, orderby, request): - html_parser = HTMLParser.HTMLParser() - response.set_cookie(key='count', value=pagesize, path=request.path) - response.set_cookie(key='orderby', value=html_parser.unescape(orderby), path=request.path) - return response +def _set_parameters_values(pagesize, orderby, request): + from django.core.urlresolvers import resolve + current_url = resolve(request.path_info).url_name + request.session['%s_count' % current_url] = pagesize + request.session['%s_orderby' % current_url] =orderby # date range: normalize GUI's dd/mm/yyyy to date object def _normalize_input_date(date_str,default):
@@ -563,7 +562,7 @@ def recipe_packages(request, build_id, recipe_id): ] } response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def target_common( request, build_id, target_id, variant ):
@@ -737,7 +736,7 @@ eans multiple licenses exist that cover different parts of the source', context['tablecols'].append(tc_layerDir) response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def target( request, build_id, target_id ):
@@ -1129,7 +1128,7 @@ def tasks_common(request, build_id, variant, task_anchor): context['tablecols'].append(tc_log) response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def tasks(request, build_id):
@@ -1265,7 +1264,7 @@ def recipes(request, build_id): response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def configuration(request, build_id):
@@ -1378,7 +1377,7 @@ def configvars(request, build_id): } response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def bpackage(request, build_id):
@@ -1479,7 +1478,7 @@ def bpackage(request, build_id): context['tablecols'].append(tc_layerDir) response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def bfile(request, build_id, package_id):
@@ -1640,7 +1639,7 @@ def package_built_detail(request, build_id, package_id): context['disable_sort'] = True; response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def package_built_dependencies(request, build_id, package_id):
@@ -1705,7 +1704,7 @@ def package_included_detail(request, build_id, target_id, package_id): if paths.all().count() < 2: context['disable_sort'] = True response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def package_included_dependencies(request, build_id, target_id, package_id):
@@ -1780,7 +1779,7 @@ def package_included_reverse_dependencies(request, build_id, target_id, package_ if objects.all().count() < 2: context['disable_sort'] = True response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def image_information_dir(request, build_id, target_id, packagefile_id):
@@ -1842,7 +1841,7 @@ if toastermain.settings.MANAGED: return _redirect_parameters( builds, request.GET, e.response) response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response
@@ -2748,7 +2747,7 @@ if toastermain.settings.MANAGED: return _redirect_parameters(projectbuilds, request.GET, e.response, pid = pid) response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response
@@ -2967,7 +2966,7 @@ if toastermain.settings.MANAGED: } response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response def buildrequestdetails(request, pid, brid):
@@ -3171,7 +3170,7 @@ else: context.update(context_date) response = render(request, template, context) - _save_parameters_cookies(response, pagesize, orderby, request) + _set_parameters_values(pagesize, orderby, request) return response
Signature · 1 line
1.9.1
PATCH 15/22 toastergui: tests for the all-projects API point
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
This patch adds Django tests that verify that the 'all-projects' page returns a valid HTML page when invoked normally, containing the project name; and valid JSON containing API-needed fields if the GET parameter format is set to "json"
--- lib/toaster/toastergui/tests.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 lib/toaster/toastergui/tests.py
lib/toaster/toastergui/tests.py new file +36 -0
@@ -0,0 +1,36 @@ +from django.test import TestCase +from django.core.urlresolvers import reverse +from orm.models import Project, Release, BitbakeVersion, Build + +class AllProjectsViewTestCase(TestCase): + TEST_PROJECT_NAME = "test project" + + def setUp(self): + bbv, created = BitbakeVersion.objects.get_or_create(name="test bbv", giturl="/tmp/", branch="master", dirpath="") + release, created = Release.objects.get_or_create(name="test release", bitbake_version = bbv) + Project.objects.create_project(name=AllProjectsViewTestCase.TEST_PROJECT_NAME, release=release) + + def test_get_base_call_returns_html(self): + 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(AllProjectsViewTestCase.TEST_PROJECT_NAME in response.content) + + def test_get_json_call_returns_json(self): + response = self.client.get(reverse('all-projects'), {"format": "json"}, follow=True) + self.assertEqual(response.status_code, 200) + self.assertTrue(response['Content-Type'].startswith('application/json')) + try: + import json + data = json.loads(response.content) + except: + self.fail("Response %s is not json-loadable" % response.content) + + self.assertTrue("list" in data) + self.assertTrue(AllProjectsViewTestCase.TEST_PROJECT_NAME in map(lambda x: x["name"], data["list"])) + self.assertTrue("id" in data["list"][0]) + self.assertTrue("xhrProjectDataTypeaheadUrl" in data["list"][0]) + self.assertTrue("projectPageUrl" in data["list"][0]) + self.assertTrue("xhrProjectEditUrl" in data["list"][0]) + self.assertTrue("projectBuildUrl" in data["list"][0])
Signature · 1 line
1.9.1
PATCH 16/22 toastergui: add tests for xhr_datatypeahead calls
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
In order to make sure we replace the xhr_datatypeahead calls with similar APIs, we add tests that validate the data returned by the xhr_datatypeahead. A copy of these tests will be enabled for each type of datatypeahead that will be replaced.
--- lib/toaster/toastergui/tests.py | 50 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-)
lib/toaster/toastergui/tests.py +46 -4
@@ -1,14 +1,18 @@ from django.test import TestCase from django.core.urlresolvers import reverse from orm.models import Project, Release, BitbakeVersion, Build +from orm.models import ReleaseLayerSourcePriority, LayerSource, Layer, Layer_Version -class AllProjectsViewTestCase(TestCase): +class ProvisionedProjectTestCase(TestCase): TEST_PROJECT_NAME = "test project" def setUp(self): - bbv, created = BitbakeVersion.objects.get_or_create(name="test bbv", giturl="/tmp/", branch="master", dirpath="") - release, created = Release.objects.get_or_create(name="test release", bitbake_version = bbv) - Project.objects.create_project(name=AllProjectsViewTestCase.TEST_PROJECT_NAME, release=release) + self.bbv, created = BitbakeVersion.objects.get_or_create(name="test bbv", giturl="/tmp/", branch="master", dirpath="") + self.release, created = Release.objects.get_or_create(name="test release", bitbake_version = self.bbv) + self.project = Project.objects.create_project(name=AllProjectsViewTestCase.TEST_PROJECT_NAME, release=self.release) + + +class AllProjectsViewTestCase(ProvisionedProjectTestCase): def test_get_base_call_returns_html(self): response = self.client.get(reverse('all-projects'), follow=True)
@@ -21,16 +25,54 @@ class AllProjectsViewTestCase(TestCase): response = self.client.get(reverse('all-projects'), {"format": "json"}, follow=True) self.assertEqual(response.status_code, 200) self.assertTrue(response['Content-Type'].startswith('application/json')) + try: import json data = json.loads(response.content) except: self.fail("Response %s is not json-loadable" % response.content) + self.assertTrue("error" in data) + self.assertEqual(data["error"], "ok") self.assertTrue("list" in data) + self.assertTrue(AllProjectsViewTestCase.TEST_PROJECT_NAME in map(lambda x: x["name"], data["list"])) self.assertTrue("id" in data["list"][0]) self.assertTrue("xhrProjectDataTypeaheadUrl" in data["list"][0]) self.assertTrue("projectPageUrl" in data["list"][0]) self.assertTrue("xhrProjectEditUrl" in data["list"][0]) self.assertTrue("projectBuildUrl" in data["list"][0]) + +class ProvisionedLayersProjectTestCase(ProvisionedProjectTestCase): + LAYER_NAME = "base-layer" + def setUp(self): + super(ProvisionedLayersProjectTestCase, self).setUp() + self.layersource, created = LayerSource.objects.get_or_create(sourcetype = LayerSource.TYPE_IMPORTED) + self.releaselayersourcepriority, created = ReleaseLayerSourcePriority.objects.get_or_create(release = self.release, layer_source = self.layersource) + self.layer, created = Layer.objects.get_or_create(name=XHRDataTypeAheadTestCase.LAYER_NAME, layer_source=self.layersource, vcs_url="/tmp/") + self.lv, created = Layer_Version.objects.get_or_create(layer = self.layer, project = self.project, layer_source=self.layersource, commit="master") + + +class XHRDataTypeAheadTestCase(ProvisionedLayersProjectTestCase): + + def setUp(self): + super(XHRDataTypeAheadTestCase, self).setUp() + self.assertTrue(self.lv in self.project.compatible_layerversions()) + + def test_xhr_datatypeahead_layer(self): + response = self.client.get(reverse('xhr_datatypeahead', args=(self.project.id,)), {"type": "layers"}) + self.assertEqual(response.status_code, 200) + self.assertTrue(response['Content-Type'].startswith('application/json')) + + try: + import json + data = json.loads(response.content) + except: + self.fail("Response %s is not json-loadable" % response.content) + + self.assertTrue("error" in data) + self.assertEqual(data["error"], "ok") + self.assertTrue("list" in data) + self.assertTrue(len(data["list"]) > 0) + + self.assertTrue(XHRDataTypeAheadTestCase.LAYER_NAME in map(lambda x: x["name"], data["list"]))
Signature · 1 line
1.9.1
PATCH 17/22 bldcontrol: Fix all failing unit tests
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
This fixes the unit tests for the bldcontrol it requires the implementation of a new Exception type so that a known Exception can be handled. Also fixed is the path to the toaster conf files so that the test doesn't need to be run from the top level directory and the ability to specify the values of TTS_SOURCE_DIR and TTS_BUILD_DIR and TTS_TEST_ADDRESS used for testing.
Edited by Alex Damian to correct the rebasing of the localhostbecontroller.py file.
--- lib/toaster/bldcontrol/localhostbecontroller.py | 2 +- lib/toaster/bldcontrol/sshbecontroller.py | 5 +- lib/toaster/bldcontrol/tests.py | 84 ++++++++++++++++--------- 3 files changed, 61 insertions(+), 30 deletions(-)
lib/toaster/bldcontrol/localhostbecontroller.py +1 -1
@@ -117,7 +117,7 @@ class LocalhostBEController(BuildEnvironmentController): f.seek(0, 2) # jump to the end toaster_ui_log_filelength = f.tell() - cmd = "bash -c \"source %s/oe-init-build-env %s 2>&1 >toaster_server.log && bitbake --read conf/toaster-pre.conf --postread conf/toaster.conf --server-only -t xmlrpc -B 0.0.0.0:0 2>&1 >>toaster_server.log \"" % (self.pokydirname, self.be.builddir) + cmd = "bash -c \"source %s/oe-init-build-env %s 2>&1 >toaster_server.log && bitbake --read %s/conf/toaster-pre.conf --postread %s/conf/toaster.conf --server-only -t xmlrpc -B 0.0.0.0:0 2>&1 >>toaster_server.log \"" % (self.pokydirname, self.be.builddir, self.be.builddir, self.be.builddir) port = "-1" logger.debug("localhostbecontroller: starting builder \n%s\n" % cmd)
lib/toaster/bldcontrol/sshbecontroller.py +4 -1
@@ -31,6 +31,9 @@ from toastermain import settings from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException +class NotImplementedException(Exception): + pass + def DN(path): return "/".join(path.split("/")[0:-1])
@@ -125,7 +128,7 @@ class SSHBEController(BuildEnvironmentController): # set layers in the layersource - raise Exception("Not implemented: SSH setLayers") + raise NotImplementedException("Not implemented: SSH setLayers") # 3. configure the build environment, so we have a conf/bblayers.conf assert self.pokydirname is not None self._setupBE()
lib/toaster/bldcontrol/tests.py +56 -28
154 lines not on this page · raw message
Signature · 1 line
1.9.1
PATCH 18/22 orm: Fix all failing unit test
In reply to Alex DAMIANFrom: Michael Wood <[email protected]>
This fixes all the unit tests for the orm. Also added is the ability to set a custom Layer index if you want to avoid using the public one by specifying TTS_LAYER_INDEX
--- lib/toaster/orm/models.py | 5 ++++- lib/toaster/orm/tests.py | 32 ++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 13 deletions(-)
lib/toaster/orm/models.py +4 -1
@@ -782,8 +782,11 @@ class LayerIndexLayerSource(LayerSource): print "EE: could not connect to %s, skipping update: %s\n%s" % (self.apiurl, e, traceback.format_exc(e)) return - # update branches; only those that we already have names listed in the Releases table + # update branches; only those that we already have names listed in the + # Releases table whitelist_branch_names = map(lambda x: x.branch_name, Release.objects.all()) + if len(whitelist_branch_names) == 0: + raise Exception("Failed to make list of branches to fetch") print "Fetching branches" branches_info = _get_json_response(apilinks['branches']
lib/toaster/orm/tests.py +20 -12
@@ -1,4 +1,4 @@ -from django.test import TestCase +from django.test import TestCase, TransactionTestCase from orm.models import LocalLayerSource, LayerIndexLayerSource, ImportedLayerSource, LayerSource from orm.models import Branch
@@ -7,6 +7,10 @@ from orm.models import Release, ReleaseLayerSourcePriority, BitbakeVersion from django.utils import timezone +import os + +# set TTS_LAYER_INDEX to the base url to use a different instance of the layer index + # tests to verify inheritance for the LayerSource proxy-inheritance classes class LayerSourceVerifyInheritanceSaveLoad(TestCase): def test_object_creation(self):
@@ -29,17 +33,20 @@ class LayerSourceVerifyInheritanceSaveLoad(TestCase): self.assertRaises(Exception, duplicate) -# test to verify the layer source update functionality for layerindex. edit to pass the URL to a layerindex application -class LILSUpdateTestCase(TestCase): - def test_update(self): - lils = LayerSource.objects.create(name = "b1", sourcetype = LayerSource.TYPE_LAYERINDEX, apiurl = "http://adamian-desk.local:8080/layerindex/api/") - lils.update() +class LILSUpdateTestCase(TransactionTestCase): + def setUp(self): + # create release + bbv = BitbakeVersion.objects.create(name="master", giturl="git://git.openembedded.org/bitbake") + release = Release.objects.create(name="default-release", bitbake_version = bbv, branch_name = "master") - # run second update - # lils.update() + def test_update(self): + layer_index_url = os.getenv("TTS_LAYER_INDEX") + if layer_index_url == None: + print "Using layers.openembedded.org for layer index. override with TTS_LAYER_INDEX enviroment variable" + layer_index_url = "http://layers.openembedded.org/" - # print vars(lils) - #print map(lambda x: vars(x), Branch.objects.all()) + lils = LayerSource.objects.create(name = "b1", sourcetype = LayerSource.TYPE_LAYERINDEX, apiurl = layer_index_url + "layerindex/api/") + lils.update() # run asserts self.assertTrue(lils.branch_set.all().count() > 0, "update() needs to fetch some branches")
@@ -59,6 +66,7 @@ class LayerVersionEquivalenceTestCase(TestCase): # attach layer source to release ReleaseLayerSourcePriority.objects.create(release = release, layer_source = ls, priority = 1) + # create layer attach self.layer = Layer.objects.create(name="meta-testlayer", layer_source = ls) # create branch
@@ -84,7 +92,7 @@ class LayerVersionEquivalenceTestCase(TestCase): def test_dual_layersource(self): # if we have two layers with the same name, from different layer sources, we expect both layers in, in increasing priority of the layer source - ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL) + ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL, apiurl="test") # assign a lower priority for the second layer source Release.objects.get(name="default-release").releaselayersourcepriority_set.create(layer_source = ls2, priority = 2)
@@ -149,7 +157,7 @@ class ProjectLVSelectionTestCase(TestCase): def test_dual_layersource(self): # if we have two layers with the same name, from different layer sources, we expect both layers in, in increasing priority of the layer source - ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL) + ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL, apiurl="testing") # assign a lower priority for the second layer source Release.objects.get(name="default-release").releaselayersourcepriority_set.create(layer_source = ls2, priority = 2)
Signature · 1 line
1.9.1
PATCH 19/22 toaster: enable no browser start
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
We enable a "nobrowser" parameter that inhibits
the launch of a browser when toaster starts.
This is useful for integration with automated startup scripts and enables headless testing.
--- bin/toaster | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
bin/toaster +5 -1
@@ -154,6 +154,7 @@ NOTOASTERUI=0 WEBSERVER=1 TOASTER_BRBE="" WEB_PORT="8000" +NOBROWSER=0 for param in $*; do case $param in
@@ -163,6 +164,9 @@ for param in $*; do noweb ) WEBSERVER=0 ;; + nobrowser ) + NOBROWSER=1 + ;; brbe=* ) TOASTER_BRBE=$'\n'"TOASTER_BRBE=\""${param#*=}"\"" ;;
@@ -227,7 +231,7 @@ if [ `basename \"$0\"` = `basename \"${SRCFILE}\"` ]; then cleanup exit 1 fi - if [ $WEBSERVER -gt 0 ]; then + if [ $WEBSERVER -gt 0 ] && [ $NOBROWSER -eq 0 ] ; then echo "Starting browser..." xdg-open http://127.0.0.1:$WEB_PORT/ >/dev/null 2>&1 & fi
Signature · 1 line
1.9.1
PATCH 20/22 bitbake/: toaster tts: fix tests
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
--- lib/toaster/contrib/tts/backlog.txt | 2 -- lib/toaster/contrib/tts/launcher.py | 2 +- lib/toaster/contrib/tts/runner.py | 2 +- lib/toaster/contrib/tts/tests.py | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 lib/toaster/contrib/tts/backlog.txt
lib/toaster/contrib/tts/backlog.txt deleted +0 -2
@@ -1,2 +0,0 @@ -michaelw/toaster-frontend-cleanups|PENDING -adamian/test_0|PENDING
lib/toaster/contrib/tts/launcher.py +1 -1
@@ -87,7 +87,7 @@ if __name__ == "__main__": errtext = None out = None try: - out = shellutils.run_shell_cmd("./runner.py %s" % next_task) + out = shellutils.run_shell_cmd("%s %s" % (os.path.join(os.path.dirname(__file__), "runner.py"), next_task)) pass except ShellCmdException as e: print("Failed while running the test runner: %s", e)
lib/toaster/contrib/tts/runner.py +1 -1
@@ -168,7 +168,7 @@ def read_settings(): # cleanup ! def clean_up(testdir): # TODO: delete the test dir - #run_shell_cmd("rm -rf -- '%s'" % testdir) + run_shell_cmd("rm -rf -- '%s'" % testdir) pass if __name__ == "__main__":
lib/toaster/contrib/tts/tests.py +1 -1
@@ -44,7 +44,7 @@ class TestPySystemStart(unittest.TestCase): def test_start_interactive_mode(self): try: - run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start && source toaster stop'" % config.testdir, config.testdir) + run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start webport=56789 && source toaster stop'" % config.testdir, config.testdir) except ShellCmdException as e: self.fail("Failed starting interactive mode: %s" % (e))
Signature · 1 line
1.9.1
PATCH 21/22 toaster tests: enable url check test
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
Integrate the HTML5 validation as a test instead of calling a separate script. This enables us to get the HTML5 validation report as part of patch-level testing.
gitignore the cache directory created by the http client
--- lib/toaster/contrib/tts/config.py | 21 ++++++++++++++ lib/toaster/contrib/tts/runner.py | 2 +- lib/toaster/contrib/tts/tests.py | 55 +++++++++++++++++++++++++++++++++++-- lib/toaster/contrib/tts/urlcheck.py | 41 +++++++++++++-------------- 4 files changed, 94 insertions(+), 25 deletions(-)
lib/toaster/contrib/tts/config.py +21 -0
@@ -22,6 +22,7 @@ # everything that would be a global variable goes here import os, sys, logging +import socket LOGDIR = "log" SETTINGS_FILE = os.path.join(os.path.dirname(__file__), "settings.json")
@@ -29,6 +30,26 @@ TEST_DIR_NAME = "tts_testdir" OWN_PID = os.getpid() +W3C_VALIDATOR = "http://icarus.local/w3c-validator/check?doctype=HTML5&uri=" + +#TODO assign port dynamically +TOASTER_PORT=56789 + +#we parse the w3c URL to know where to connect + +import urlparse + +def get_public_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + p = urlparse.urlparse("http://icarus.local/w3c-validator/check?doctype=HTML5&uri=") + s.connect(( p.netloc, 80 if p.port is None else p.port)) + hn = s.getsockname()[0] + s.close() + return hn + +TOASTER_BASEURL="http://%s:%d/" % (get_public_ip(), TOASTER_PORT) + + OWN_EMAIL_ADDRESS = "Toaster Testing Framework <[email protected]>" REPORT_EMAIL_ADDRESS = "[email protected]"
lib/toaster/contrib/tts/runner.py +1 -1
@@ -181,7 +181,7 @@ if __name__ == "__main__": no_failures = 1 try: if options.testdir is not None and os.path.exists(options.testdir): - testdir = options.testdir + testdir = os.path.abspath(options.testdir) config.logger.info("No checkout, using %s" % testdir) else: need_cleanup = True
lib/toaster/contrib/tts/tests.py +53 -2
73 lines not on this page · raw message
lib/toaster/contrib/tts/urlcheck.py +19 -22
57 lines not on this page · raw message
Signature · 1 line
1.9.1
PATCH 22/22 toaster: more HTML5 fixes
In reply to Alex DAMIANFrom: Alexandru DAMIAN <[email protected]>
More HTML5 fixes highlighted by tests on new data.
--- lib/toaster/toastergui/templates/builddashboard.html | 2 +- lib/toaster/toastergui/templates/configvars.html | 2 +- lib/toaster/toastergui/templates/recipe.html | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-)
lib/toaster/toastergui/templates/builddashboard.html +1 -1
@@ -209,6 +209,7 @@ <a class="error" href="{% url "task" build.id exectask.0.id %}"> {{exectask.0.recipe.name}} <span class="task-name">{{exectask.0.task_name}}</span> + </a> {% if MANAGED and build.project %} <a href="{% url 'build_artifact' build.id "tasklogfile" exectask.0.id %}">
@@ -216,7 +217,6 @@ </a> {% endif %} - </a> {% elif exectask.count > 1%} <a class="error" href="{% url "tasks" build.id %}?filter=outcome%3A4">{{exectask.count}}</a> {% endif %}
lib/toaster/toastergui/templates/configvars.html +1 -1
@@ -56,7 +56,7 @@ <td class="file"><a data-toggle="modal" href="#variable-{{variable.pk}}"> {% if variable.vhistory.all %} {% for path in variable.vhistory.all|filter_setin_files:file_filter %} - {{path|cut_path_prefix:dirstostrip}}<p> + {{path|cut_path_prefix:dirstostrip}}<br/> {% endfor %} {% endif %} </a></td>
lib/toaster/toastergui/templates/recipe.html +2 -0
@@ -87,6 +87,7 @@ {% else %} <table class="table table-bordered table-hover"> <thead> + <tr> <th> <i class="icon-question-sign get-help" title="The running sequence of each task in the build"></i> Order
@@ -107,6 +108,7 @@ <i class="icon-question-sign get-help" title="This column tells you if a task tried to restore output from the <code>sstate-cache</code> directory or mirrors, and reports the result: Succeeded, Failed or File not in cache"></i> Cache attempt </th> + </tr> </thead> <tbody>
Signature · 1 line
1.9.1