Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package openQA for openSUSE:Factory checked 
in at 2026-03-14 22:22:35
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openQA (Old)
 and      /work/SRC/openSUSE:Factory/.openQA.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openQA"

Sat Mar 14 22:22:35 2026 rev:824 rq:1338820 version:5.1773427330.0b172206

Changes:
--------
--- /work/SRC/openSUSE:Factory/openQA/openQA.changes    2026-03-13 
21:17:01.617849736 +0100
+++ /work/SRC/openSUSE:Factory/.openQA.new.8177/openQA.changes  2026-03-14 
22:23:45.460517043 +0100
@@ -1,0 +2,27 @@
+Fri Mar 13 22:08:20 UTC 2026 - [email protected]
+
+- Update to version 5.1773427330.0b172206:
+  * fix: Display GitHub bugrefs correctly when used within a label
+  * refactor: Improve style in `t/39-scheduled_products-table.t`
+  * feat: improve responsiveness of tabs in job details view
+  * ci(helm): override pullPolicy when install helm via ct
+  * test: Consider everything under `lib/OpenQA/Schema` covered
+  * test: Cover generating Gravatar URLs
+  * test: Cover handling failed job cancellation when scheduling iso
+  * feat: add zypper clean command in base container
+  * test: Cover computing Git log diff
+  * test: Cover adding logs to result file list
+  * refactor: Simplify and slightly improve `create_asset`
+  * refactor: Simplify error handling in function for appending job logs
+  * test: Cover setting and deleting job properties
+  * test: Cover error handling when duplicating jobs
+  * test: Mark error handling in `_hashref` as uncoverable
+  * test: Cover remaining lines of `JobModules.pm`
+  * refactor: Remove unused function `locked_by_jobs`
+  * test: Cover removing test suite defaults
+  * test: Cover rendering description of parent job group
+  * test: Consider everything under `lib/OpenQA/Script/` covered
+  * test: Cover `openqa_baseurl` used by clone script
+  * test: Cover handling unexpected return code in clone script
+
+-------------------------------------------------------------------

Old:
----
  openQA-5.1773333964.ffc5eff5.obscpio

New:
----
  openQA-5.1773427330.0b172206.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ openQA-client-test.spec ++++++
--- /var/tmp/diff_new_pack.rtYZIL/_old  2026-03-14 22:23:48.296634531 +0100
+++ /var/tmp/diff_new_pack.rtYZIL/_new  2026-03-14 22:23:48.304634862 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-client
 Name:           %{short_name}-test
-Version:        5.1773333964.ffc5eff5
+Version:        5.1773427330.0b172206
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-devel-test.spec ++++++
--- /var/tmp/diff_new_pack.rtYZIL/_old  2026-03-14 22:23:48.604647291 +0100
+++ /var/tmp/diff_new_pack.rtYZIL/_new  2026-03-14 22:23:48.612647622 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-devel
 Name:           %{short_name}-test
-Version:        5.1773333964.ffc5eff5
+Version:        5.1773427330.0b172206
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-test.spec ++++++
--- /var/tmp/diff_new_pack.rtYZIL/_old  2026-03-14 22:23:48.872658394 +0100
+++ /var/tmp/diff_new_pack.rtYZIL/_new  2026-03-14 22:23:48.880658725 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA
 Name:           %{short_name}-test
-Version:        5.1773333964.ffc5eff5
+Version:        5.1773427330.0b172206
 Release:        0
 Summary:        Test package for openQA
 License:        GPL-2.0-or-later

++++++ openQA-worker-test.spec ++++++
--- /var/tmp/diff_new_pack.rtYZIL/_old  2026-03-14 22:23:49.116668502 +0100
+++ /var/tmp/diff_new_pack.rtYZIL/_new  2026-03-14 22:23:49.116668502 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-worker
 Name:           %{short_name}-test
-Version:        5.1773333964.ffc5eff5
+Version:        5.1773427330.0b172206
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA.spec ++++++
--- /var/tmp/diff_new_pack.rtYZIL/_old  2026-03-14 22:23:49.196671816 +0100
+++ /var/tmp/diff_new_pack.rtYZIL/_new  2026-03-14 22:23:49.200671982 +0100
@@ -99,7 +99,7 @@
 %define devel_requires %devel_no_selenium_requires chromedriver
 
 Name:           openQA
-Version:        5.1773333964.ffc5eff5
+Version:        5.1773427330.0b172206
 Release:        0
 Summary:        The openQA web-frontend, scheduler and tools
 License:        GPL-2.0-or-later

++++++ openQA-5.1773333964.ffc5eff5.obscpio -> 
openQA-5.1773427330.0b172206.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/assets/javascripts/render.js 
new/openQA-5.1773427330.0b172206/assets/javascripts/render.js
--- old/openQA-5.1773333964.ffc5eff5/assets/javascripts/render.js       
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/assets/javascripts/render.js       
2026-03-13 19:42:10.000000000 +0100
@@ -212,7 +212,53 @@
   return E('tr', [component, result, links], {id: rowid});
 }
 
-function renderModuleTable(container, response) {
+// Default batch size for chunked rendering to balance responsiveness and 
overhead
+const DEFAULT_BATCH_SIZE = 50;
+
+/**
+ * Process an array of items in batches using requestAnimationFrame for 
cooperative multitasking.
+ * This prevents blocking the UI thread during long-running operations.
+ *
+ * @param {Array} items - Array of items to process
+ * @param {Function} processor - Function called for each item with (item, 
index) as arguments
+ * @param {Object} options - Processing options
+ * @param {number} [options.batchSize=50] - Number of items to process per 
frame
+ * @param {Function} [options.shouldContinue=() => true] - Callback returning 
boolean to control interruption
+ * @returns {Promise<boolean>} Promise resolving to true if completed, false 
if interrupted
+ * @throws {Error} If processor throws an error during execution
+ */
+function batchProcess(items, processor, options = {}) {
+  const {batchSize = DEFAULT_BATCH_SIZE, shouldContinue = () => true} = 
options;
+  let currentIndex = 0;
+
+  return new Promise((resolve, reject) => {
+    function processNextBatch() {
+      if (!shouldContinue()) {
+        resolve(false); // Interrupted
+        return;
+      }
+
+      const end = Math.min(currentIndex + batchSize, items.length);
+      try {
+        for (; currentIndex < end; currentIndex++) {
+          processor(items[currentIndex], currentIndex);
+        }
+      } catch (err) {
+        reject(err);
+        return;
+      }
+
+      if (currentIndex < items.length) {
+        requestAnimationFrame(processNextBatch);
+      } else {
+        resolve(true); // Completed
+      }
+    }
+    processNextBatch();
+  });
+}
+
+async function renderModuleTable(container, response, shouldContinue = () => 
true) {
   container.innerHTML = response.snippets.header;
 
   const E = createElement;
@@ -226,7 +272,7 @@
   }
 
   if (response.modules === undefined || response.modules === null) {
-    return;
+    return Promise.resolve(true);
   }
 
   const thead = E('thead', [
@@ -236,17 +282,18 @@
 
   container.appendChild(E('table', [thead, tbody], {id: 'results', class: 
'table table-striped'}));
 
-  for (const idx in response.modules) {
-    const module = response.modules[idx];
-
-    if (module.category) {
-      tbody.appendChild(
-        E('tr', [E('td', [E('i', [], {class: 'fa fa-folder-open'}), '\u00a0' + 
module.category], {colspan: 3})])
-      );
-    }
-
-    tbody.appendChild(renderModuleRow(module, response.snippets));
-  }
+  return batchProcess(
+    response.modules,
+    module => {
+      if (module.category) {
+        tbody.appendChild(
+          E('tr', [E('td', [E('i', [], {class: 'fa fa-folder-open'}), '\u00a0' 
+ module.category], {colspan: 3})])
+        );
+      }
+      tbody.appendChild(renderModuleRow(module, response.snippets));
+    },
+    {shouldContinue}
+  );
 }
 
 function renderJobLink(jobId) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/assets/javascripts/running.js 
new/openQA-5.1773427330.0b172206/assets/javascripts/running.js
--- old/openQA-5.1773333964.ffc5eff5/assets/javascripts/running.js      
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/assets/javascripts/running.js      
2026-03-13 19:42:10.000000000 +0100
@@ -142,7 +142,8 @@
       const previewContainer = 
document.getElementById('preview_container_out');
       const resultCells = resultsTable.getElementsByClassName('result');
       testStatus.textDataMissing = false;
-      modules.forEach(function (module, moduleIndex) {
+
+      batchProcess(modules, (module, moduleIndex) => {
         const resultCell = resultCells[moduleIndex];
         if (!resultCell) {
           return;
@@ -164,16 +165,21 @@
         }
         // actually update the row
         resultRow.replaceWith(renderModuleRow(module, snippets));
-      });
-
-      testStatus.running = newStatus.running;
-      developerMode.detailsForCurrentModuleUploaded = false;
-      updateDeveloperMode();
+      })
+        .then(completed => {
+          if (!completed) return;
+          testStatus.running = newStatus.running;
+          developerMode.detailsForCurrentModuleUploaded = false;
+          updateDeveloperMode();
 
-      // reload broken thumbnails one last time
-      if (newState === 'done') {
-        reloadBrokenThumbnails(true);
-      }
+          // reload broken thumbnails one last time
+          if (newState === 'done') {
+            reloadBrokenThumbnails(true);
+          }
+        })
+        .catch(error => {
+          console.error('Error batch processing test modules:', error);
+        });
     })
     .catch(error => {
       console.log('ERROR: modlist fail');
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/assets/javascripts/test_result.js 
new/openQA-5.1773427330.0b172206/assets/javascripts/test_result.js
--- old/openQA-5.1773333964.ffc5eff5/assets/javascripts/test_result.js  
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/assets/javascripts/test_result.js  
2026-03-13 19:42:10.000000000 +0100
@@ -487,6 +487,15 @@
   }
 }
 
+function renderTabContent(tabConfig, response) {
+  const customRenderer = tabConfig.renderContents;
+  if (customRenderer) {
+    return customRenderer.call(tabConfig, response);
+  }
+  tabConfig.panelElement.innerHTML = response;
+  return Promise.resolve();
+}
+
 function loadTabPanelElement(tabName, tabConfig) {
   const tabPanelElement = document.getElementById(tabName);
   if (!tabPanelElement) {
@@ -497,21 +506,27 @@
     return false;
   }
   tabConfig.panelElement = tabPanelElement; // for easier access in custom 
renderers
-  fetch(ajaxUrl, {method: 'GET'})
+  if (tabConfig._abortController) {
+    tabConfig._abortController.abort();
+  }
+  tabConfig._abortController = new AbortController();
+  fetch(ajaxUrl, {method: 'GET', signal: tabConfig._abortController.signal})
     .then(response => {
       if (!response.ok) throw `Server returned ${response.status}: 
${response.statusText}`;
       if (response.headers.get('Content-Type').includes('application/json')) 
return response.json();
       return response.text();
     })
     .then(response => {
-      const customRenderer = tabConfig.renderContents;
-      if (customRenderer) {
-        return customRenderer.call(tabConfig, response);
+      if (!tabConfig.isActive) {
+        tabConfig._deferredResponse = response;
+        return;
       }
-      tabPanelElement.innerHTML = response;
+      tabConfig._deferredResponse = undefined;
+      return renderTabContent(tabConfig, response);
     })
     .catch(error => {
-      console.error(error);
+      if (error.name === 'AbortError') return;
+      console.error(`Error loading tab '${tabName}':`, error);
       const customRenderer = tabConfig.renderError;
       if (customRenderer) {
         return customRenderer.call(tabConfig, error);
@@ -542,6 +557,17 @@
   if (!tabConfig.initialized) {
     return (tabConfig.initialized = loadTabPanelElement(tabName, tabConfig));
   }
+  if (tabConfig._deferredResponse) {
+    const response = tabConfig._deferredResponse;
+    renderTabContent(tabConfig, response)
+      .then(() => {
+        tabConfig._deferredResponse = undefined;
+      })
+      .catch(error => {
+        console.error(`Error rendering deferred content for tab 
'${tabName}':`, error);
+        tabConfig._deferredResponse = undefined;
+      });
+  }
   const showHandler = tabConfig.onShow;
   if (showHandler) {
     return showHandler.call(tabConfig);
@@ -557,6 +583,10 @@
     return false;
   }
   tabConfig.isActive = false;
+  if (!tabConfig.initialized && tabConfig._abortController) {
+    tabConfig._abortController.abort();
+    tabConfig._abortController = undefined;
+  }
   const hideHandler = tabConfig.onHide;
   if (hideHandler) {
     return hideHandler.call(tabConfig);
@@ -767,76 +797,27 @@
   return commits;
 }
 
-function renderTestModules(response) {
-  this.hasContents = true;
-  renderModuleTable(this.panelElement, response);
-
-  // load the embedded logfiles (autoinst-log.txt); assume that in this case 
no test modules are available and skip further processing
-  if (this.panelElement.getElementsByClassName('embedded-logfile').length > 0) 
{
-    loadEmbeddedLogFiles();
-    return;
-  }
-
-  setupLazyLoadingFailedSteps();
+function setupTestDetailsFilter(tabConfig) {
+  if (tabConfig._hasFilterHandlers) return;
 
-  // enable the external tab if there are text results
-  // note: It would be more efficient to query "regular details" and external 
results in one go because both
-  //       are just a different representation of the same data.
-  if (document.getElementsByClassName('external-result-container').length) {
-    showTabNavElement('external');
-  }
-
-  // display the preview for the current step according to the hash
-  const hash = window.location.hash;
-  if (hash.search('#step/') === 0) {
-    setCurrentPreviewFromStepLinkIfPossible($("[href='" + hash + "'], 
[data-href='" + hash + "']"));
-  }
-
-  // setup event handlers for the window
-  if (!this.hasWindowEventHandlers) {
-    // setup keyboard navigation through test details
-    $(window).keydown(handleKeyDownOnTestDetails);
-
-    // ensure the size of the preview container is adjusted when the window 
size changes
-    $(window).resize(function () {
-      const currentPreview = $('.current_preview');
-      if (currentPreview.length) {
-        setCurrentPreview($('.current_preview'), true);
-      }
-    });
-    this.hasWindowEventHandlers = true;
-  }
-
-  // setup result filter, define function to apply filter changes
   const detailsFilter = $('#details-filter');
   const detailsNameFilter = $('#details-name-filter');
   const detailsFailedOnlyFilter = $('#details-only-failed-filter');
   const resultsTable = $('#results');
-  let anyFilterEnabled = false;
-  let nameFilter = '';
-  let nameFilterEnabled = false;
-  let failedOnlyFilterEnabled = false;
-  const applyFilterChanges = function (event) {
-    // determine enabled filter
-    anyFilterEnabled = !detailsFilter.hasClass('hidden');
-    if (anyFilterEnabled) {
-      nameFilter = detailsNameFilter.val();
-      nameFilterEnabled = nameFilter.length !== 0;
-      failedOnlyFilterEnabled = detailsFailedOnlyFilter.prop('checked');
-      anyFilterEnabled = nameFilterEnabled || failedOnlyFilterEnabled;
-    }
 
-    // show everything if no filter present
-    if (!anyFilterEnabled) {
+  const applyFilterChanges = () => {
+    const anyFilterEnabled = !detailsFilter.hasClass('hidden');
+    const nameFilter = detailsNameFilter.val();
+    const nameFilterEnabled = anyFilterEnabled && nameFilter.length !== 0;
+    const failedOnlyFilterEnabled = anyFilterEnabled && 
detailsFailedOnlyFilter.prop('checked');
+
+    if (!nameFilterEnabled && !failedOnlyFilterEnabled) {
       resultsTable.find('tbody tr').show();
       return;
     }
 
-    // hide all categories
     resultsTable.find('tbody tr td[colspan="3"]').parent('tr').hide();
-
-    // show/hide table rows considering filter
-    $.each(resultsTable.find('tbody .result'), function (index, td) {
+    $.each(resultsTable.find('tbody .result'), (index, td) => {
       const tdElement = $(td);
       const trElement = tdElement.parent('tr');
       const stepMaches =
@@ -846,15 +827,60 @@
     });
   };
 
-  detailsNameFilter.keyup(applyFilterChanges);
-  detailsFailedOnlyFilter.change(applyFilterChanges);
-
-  // setup filter toggle
-  $('.details-filter-toggle').on('click', function (event) {
+  detailsNameFilter.on('keyup', applyFilterChanges);
+  detailsFailedOnlyFilter.on('change', applyFilterChanges);
+  $('.details-filter-toggle').on('click', event => {
     event.preventDefault();
     detailsFilter.toggleClass('hidden');
     applyFilterChanges();
   });
+
+  tabConfig._hasFilterHandlers = true;
+}
+
+function setupTestDetailsWindowEventHandlers(tabConfig) {
+  if (tabConfig._hasWindowEventHandlers) return;
+  $(window).keydown(handleKeyDownOnTestDetails);
+  $(window).resize(() => {
+    if ($('.current_preview').length) {
+      setCurrentPreview($('.current_preview'), true);
+    }
+  });
+  tabConfig._hasWindowEventHandlers = true;
+}
+
+function renderTestModules(response) {
+  this.hasContents = true;
+  const tabConfig = this;
+
+  return renderModuleTable(this.panelElement, response, () => 
tabConfig.isActive)
+    .then(completed => {
+      if (!completed) return;
+
+      if 
(tabConfig.panelElement.getElementsByClassName('embedded-logfile').length > 0) {
+        loadEmbeddedLogFiles();
+        return;
+      }
+
+      setupLazyLoadingFailedSteps();
+
+      if (document.getElementsByClassName('external-result-container').length) 
{
+        showTabNavElement('external');
+      }
+
+      const hash = window.location.hash;
+      if (hash.search('#step/') === 0) {
+        setCurrentPreviewFromStepLinkIfPossible($("[href='" + hash + "'], 
[data-href='" + hash + "']"));
+      }
+
+      setupTestDetailsWindowEventHandlers(tabConfig);
+      setupTestDetailsFilter(tabConfig);
+    })
+    .catch(error => {
+      console.error('Error rendering test modules:', error);
+      tabConfig.panelElement.innerHTML = '';
+      tabConfig.panelElement.appendChild(document.createTextNode(`Unable to 
render test modules: ${error}`));
+    });
 }
 
 function renderExternalTab(response) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/codecov.yml 
new/openQA-5.1773427330.0b172206/codecov.yml
--- old/openQA-5.1773333964.ffc5eff5/codecov.yml        2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/codecov.yml        2026-03-13 
19:42:10.000000000 +0100
@@ -26,6 +26,8 @@
           - lib/OpenQA/WebAPI/
           - lib/OpenQA/Shared/
           - lib/OpenQA/Task/
+          - lib/OpenQA/Script/
+          - lib/OpenQA/Schema/
       tests:
         target: 100.0
         threshold: 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/container/devel:openQA:ci/base/Dockerfile 
new/openQA-5.1773427330.0b172206/container/devel:openQA:ci/base/Dockerfile
--- old/openQA-5.1773333964.ffc5eff5/container/devel:openQA:ci/base/Dockerfile  
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/container/devel:openQA:ci/base/Dockerfile  
2026-03-13 19:42:10.000000000 +0100
@@ -8,16 +8,16 @@
 
 # only dependencies for CircleCI to be able to load/save the package cache
 # and fix permissions for the unprivileged user we use
-RUN zypper -n in tar gzip sudo
+RUN zypper -n in tar gzip sudo && zypper clean -a
 
 # these are autoinst dependencies
-RUN zypper install -y gcc-c++ cmake ninja pkgconfig\(opencv4\) pkg-config 
perl\(Module::CPANfile\) pkgconfig\(fftw3\) pkgconfig\(libpng\) 
pkgconfig\(sndfile\) pkgconfig\(theoraenc\) tesseract-ocr
+RUN zypper install -y gcc-c++ cmake ninja pkgconfig\(opencv4\) pkg-config 
perl\(Module::CPANfile\) pkgconfig\(fftw3\) pkgconfig\(libpng\) 
pkgconfig\(sndfile\) pkgconfig\(theoraenc\) tesseract-ocr && zypper clean -a
 
 # openQA dependencies
-RUN zypper install -y perl-CSS-Sass ruby-devel npm python3-base 
python3-requests git-core rsync curl 'postgresql-devel>=14' 
'postgresql-server>=14' qemu qemu-tools tar xorg-x11-fonts sudo make
+RUN zypper install -y perl-CSS-Sass ruby-devel npm python3-base 
python3-requests git-core rsync curl 'postgresql-devel>=14' 
'postgresql-server>=14' qemu qemu-tools tar xorg-x11-fonts sudo make && zypper 
clean -a
 
 # openQA chromedriver for Selenium tests
-RUN zypper install -y chromedriver
+RUN zypper install -y chromedriver && zypper clean -a
 
 ENV LANG=en_US.UTF-8
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Schema/Result/JobLocks.pm 
new/openQA-5.1773427330.0b172206/lib/OpenQA/Schema/Result/JobLocks.pm
--- old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Schema/Result/JobLocks.pm       
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/lib/OpenQA/Schema/Result/JobLocks.pm       
2026-03-13 19:42:10.000000000 +0100
@@ -1,4 +1,4 @@
-# Copyright 2015 SUSE LLC
+# Copyright SUSE LLC
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 package OpenQA::Schema::Result::JobLocks;
@@ -33,16 +33,4 @@
 __PACKAGE__->set_primary_key('name', 'owner');
 __PACKAGE__->belongs_to(owner => 'OpenQA::Schema::Result::Jobs', 'owner');
 
-
-# translate job ids stored in locked_by to jobs
-sub locked_by_jobs {
-    my ($self) = @_;
-    my $rsource = $self->result_source;
-    my $schema = $rsource->schema;
-
-    return unless $self->locked_by;
-    my @locked_ids = split /,/, $self->locked_by;
-    return $schema->resultset('Jobs')->search({id => {-in => 
\@locked_ids}})->all;
-}
-
 1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Schema/Result/Jobs.pm 
new/openQA-5.1773427330.0b172206/lib/OpenQA/Schema/Result/Jobs.pm
--- old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Schema/Result/Jobs.pm   
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/lib/OpenQA/Schema/Result/Jobs.pm   
2026-03-13 19:42:10.000000000 +0100
@@ -450,7 +450,7 @@
             $hashref{$field} = $obj->$field->datetime();
         }
         else {
-            die "unknown field type: $ref";
+            die "unknown field type: $ref";    # uncoverable statement
         }
     }
 
@@ -1075,13 +1075,9 @@
     my $path = $self->worker->get_property('WORKER_TMPDIR');
     return unless -d $path;    # we can't help
     $path .= "/$file_name";
-    if (open my $fd, '>>', $path) {
-        print $fd $log->{data};
-        close $fd;
-    }
-    else {
-        print STDERR "can't open $path: $!\n";
-    }
+    return log_error "can't open $path: $!" unless open my $fd, '>>', $path;
+    print $fd $log->{data};
+    close $fd;
 }
 
 sub update_result ($self, $result, $state = undef) {
@@ -1417,23 +1413,17 @@
         }
 
         if ($chunk->is_last) {
-            # XXX: Watch out also apparmor permissions
-            my $sum;
-            my $real_sum;
             $last++;
 
-            # Perform weak check on last bytes if files > 250MB
-            if ($chunk->end > 250000000) {
-                $sum = $chunk->end;
-                $real_sum = -s $temp_final_file->to_string;
-            }
-            else {
-                $sum = $chunk->total_cksum;
-                $real_sum = $chunk->file_digest($temp_final_file->to_string);
-            }
+            # Perform weak check on the number of bytes if the file size is > 
250 MB
+            my $temp_final_str = $temp_final_file->to_string;
+            my ($sum, $real_sum)
+              = $chunk->end > 250000000
+              ? ($chunk->end, -s $temp_final_str)
+              : ($chunk->total_cksum, $chunk->file_digest($temp_final_str));
 
             $temp_chunk_folder->remove_tree
-              && die Mojo::Exception->new("Checksum mismatch expected $sum 
got: $real_sum ( weak check on last bytes )")
+              && die Mojo::Exception->new("Checksum mismatch expected $sum 
got: $real_sum (weak check on last bytes)")
               unless $sum eq $real_sum;
 
             $temp_final_file->move_to($final_file);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Script/CloneJob.pm 
new/openQA-5.1773427330.0b172206/lib/OpenQA/Script/CloneJob.pm
--- old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Script/CloneJob.pm      
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/lib/OpenQA/Script/CloneJob.pm      
2026-03-13 19:42:10.000000000 +0100
@@ -26,6 +26,7 @@
   create_url_handler
   split_jobid
   post_jobs
+  openqa_baseurl
 );
 
 use constant GLOBAL_SETTINGS => (qw(WORKER_CLASS _GROUP _GROUP_ID));
@@ -73,6 +74,11 @@
     }
 }
 
+sub _handle_unexpected_return_code ($tx) {    # uncoverable statement
+    warn sprintf 'unexpected return code: %s %s', $tx->res->code, 
$tx->res->message;    # uncoverable statement
+    exit 1;    # uncoverable statement
+}
+
 sub clone_job_get_job ($jobid, $url_handler, $options) {
     my $url = $url_handler->{remote_url}->clone;
     $url->path("jobs/$jobid");
@@ -84,10 +90,7 @@
         $err->{code} //= '';
         die "failed to get job '$jobid': $err->{code} $err->{message}";
     }
-    if ($tx->res->code != HTTP_OK) {
-        warn sprintf 'unexpected return code: %s %s', $tx->res->code, 
$tx->res->message;
-        exit 1;
-    }
+    _handle_unexpected_return_code($tx) unless $tx->res->code == HTTP_OK;
     my $job = $tx->res->json->{job};
     print STDERR Cpanel::JSON::XS->new->pretty->encode($job) if 
$options->{verbose};
     return $job;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Utils.pm 
new/openQA-5.1773427330.0b172206/lib/OpenQA/Utils.pm
--- old/openQA-5.1773333964.ffc5eff5/lib/OpenQA/Utils.pm        2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/lib/OpenQA/Utils.pm        2026-03-13 
19:42:10.000000000 +0100
@@ -82,7 +82,7 @@
 
 use constant UNCONSTRAINED_BUGREF_REGEX => $BUGREF_REGEX;
 use constant BUGREF_REGEX => 
qr{(?:^|(?<=<p>)|(?<=\s|,))$BUGREF_REGEX(?![\w\"])};
-use constant LABEL_REGEX => qr/\blabel:(?<match>([\w:#]+))\b/;
+use constant LABEL_REGEX => qr/\blabel:(?<match>([\w:#\/-]+))\b/;
 use constant FLAG_REGEX => qr/\bflag:(?<match>([\w:#]+))\b/;
 
 use constant ONE_SECOND_IN_MICROSECONDS => 1_000_000;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/t/06-users.t 
new/openQA-5.1773427330.0b172206/t/06-users.t
--- old/openQA-5.1773333964.ffc5eff5/t/06-users.t       2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/06-users.t       2026-03-13 
19:42:10.000000000 +0100
@@ -14,16 +14,17 @@
 
 OpenQA::Test::Database->new->create;
 my $t = Test::Mojo->new('OpenQA::WebAPI');
+my $users = $t->app->schema->resultset('Users');
 
 subtest 'new users are not ops and admins' => sub {
     my $mordred_id = 'https://openid.badguys.uk/mordred';
-    my $user = $t->app->schema->resultset('Users')->create({username => 
$mordred_id});
+    my $user = $users->create({username => $mordred_id});
     ok !$user->is_admin, 'new users are not admin by default';
     ok !$user->is_operator, 'new users are not operator by default';
 };
 
 subtest 'system user presence' => sub {
-    my $system_user = $t->app->schema->resultset('Users')->system;
+    my $system_user = $users->system;
     ok $system_user, 'system user exists';
     ok !$system_user->is_admin, 'system user is not an admin';
     ok !$system_user->is_operator, 'system user is not an operator';
@@ -31,7 +32,6 @@
 };
 
 subtest 'new user is admin if no admin is present' => sub {
-    my $users = $t->app->schema->resultset('Users');
     my $admins = $users->search({is_admin => 1});
     $_->update({is_admin => 0}) while $admins->next;
     ok !$users->search({is_admin => 1})->all, 'no admin is present';
@@ -40,4 +40,10 @@
     ok $user->is_operator, 'new user is operator by default if there was no 
admin';
 };
 
+subtest 'gravatar URL' => sub {
+    my $user = $users->create({username => 'user-without-e-mail'});
+    is $user->gravatar, '//www.gravatar.com/avatar?s=40', 'without e-mail';
+    like $users->find(1)->gravatar, 
qr|//www.gravatar.com/avatar/[a-f0-9]{32}\?d=wavatar&s=40|, 'with e-mail';
+};
+
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/t/10-jobs-results.t 
new/openQA-5.1773427330.0b172206/t/10-jobs-results.t
--- old/openQA-5.1773333964.ffc5eff5/t/10-jobs-results.t        2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/10-jobs-results.t        2026-03-13 
19:42:10.000000000 +0100
@@ -196,6 +196,9 @@
     $arbitrary_job_module->save_results(\%some_test_results);
     my $details_file = path($arbitrary_job_module->job->result_dir, 'details-' 
. $arbitrary_job_module->name . '.json');
     is_deeply decode_json($details_file->slurp), \%some_test_results, 'overall 
structure of test results preserved';
+    $some_test_results{details} = [{needles => [{json => 'foo'}]}];
+    combined_like { $arbitrary_job_module->save_results(\%some_test_results) } 
qr/foo not found/,
+      'updated for needles in "details" attempted';
 };
 
 subtest 'loading results with missing file in details' => sub {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/t/10-jobs.t 
new/openQA-5.1773427330.0b172206/t/10-jobs.t
--- old/openQA-5.1773333964.ffc5eff5/t/10-jobs.t        2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/10-jobs.t        2026-03-13 
19:42:10.000000000 +0100
@@ -1179,4 +1179,35 @@
     is_deeply [map { $_->text } $job->comments], $expected, 'linked label for 
non-bugref URL';
 };
 
+subtest 'error handling when duplicating jobs' => sub {
+    my $job = $jobs->find(99937);
+    my $job_mock = Test::MockModule->new('OpenQA::Schema::Result::Jobs', 
no_auto => 1);
+    my $res;
+
+    $job_mock->redefine(_create_clones => sub ($self, @args) { die "Rollback 
failed: foo\n" });
+    combined_like { $res = $job->duplicate } qr/unable to roll back/i, 
'failing rollback logged';
+    is $res, 'Rollback failed after failure to clone cluster of job 99937', 
'failing rollback handled';
+
+    $job_mock->redefine(_create_clones => sub ($self, @args) { die "internal 
error\n" });
+    combined_like { $res = $job->duplicate } qr/rolled back after 
error.*internal error/, 'error logged';
+    is $res, 'An internal error occurred when cloning cluster of job 99937', 
'internal error handled';
+};
+
+subtest 'setting and deleting properties' => sub {
+    my $job = $jobs->find(99937);
+    $job->set_property(foo => 42);
+    is $job->settings->find({key => 'foo'})->value, 42, 'property has been 
created';
+    $job->set_property(foo => undef);
+    is $job->settings->find({key => 'foo'}), undef, 'property has been 
deleted';
+};
+
+subtest 'adding logs to result file list, including virtio console logs' => 
sub {
+    my $job = $jobs->find(99937);
+    $job->set_property(VIRTIO_CONSOLE_NUM => 2);
+    path($job->result_dir, 'serial_terminal1.txt')->spew('foo');
+    my $files = $job->test_resultfile_list;
+    my @expected = qw(video.ogv autoinst-log.txt serial0.txt 
serial_terminal1.txt);
+    is_deeply $files, \@expected, 'list of existing result files returned' or 
always_explain $files;
+};
+
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/t/16-utils-runcmd.t 
new/openQA-5.1773427330.0b172206/t/16-utils-runcmd.t
--- old/openQA-5.1773333964.ffc5eff5/t/16-utils-runcmd.t        2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/16-utils-runcmd.t        2026-03-13 
19:42:10.000000000 +0100
@@ -14,6 +14,7 @@
 use OpenQA::Test::Case;
 use OpenQA::Test::TimeLimit '10';
 use OpenQA::Utils;
+use OpenQA::Schema::Result::Jobs;
 use Mojo::File 'tempdir';
 use Test::MockModule;
 use Test::Mojo;
@@ -108,6 +109,11 @@
           'no error (only info) logged if check returns false (despite Git 
returning 1)';
     };
 
+    subtest 'log diff computation helper of job' => sub {
+        like OpenQA::Schema::Result::Jobs::git_log_diff(undef, $empty_tmp_dir, 
'HEAD'), qr/test.*foo/s,
+          'commit and file mentioned';
+    };
+
     subtest 'cache_ref' => sub {
         my $test_file = $empty_tmp_dir->child('foo')->touch;
         my $test_file_2 = "$empty_tmp_dir/bar";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/t/35-script_clone_job.t 
new/openQA-5.1773427330.0b172206/t/35-script_clone_job.t
--- old/openQA-5.1773333964.ffc5eff5/t/35-script_clone_job.t    2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/35-script_clone_job.t    2026-03-13 
19:42:10.000000000 +0100
@@ -27,10 +27,19 @@
     has status_line => 'some status';
 }    # uncoverable statement
 
+package Test::FakeLWPUserAgentMirrorTxn {
+    use Mojo::Base -base, -signatures;
+    has error => undef;
+    has res => sub { Test::FakeLWPUserAgentMirrorResult->new(is_success => 0, 
code => 404) };
+}    # uncoverable statement
+
 package Test::FakeLWPUserAgent {
     use Mojo::Base -base, -signatures;
     has mirrored => sub { {} };
     has missing => 0;
+    has max_redirects => undef;
+
+    sub get ($self, $url) { Test::FakeLWPUserAgentMirrorTxn->new }
 
     sub mirror ($self, $from, $dest) {
         my @res
@@ -62,6 +71,14 @@
     WORKER_CLASS => 'qemu_x86_64',
 );
 
+subtest 'getting job' => sub {
+    my $clone_mock = Test::MockModule->new('OpenQA::Script::CloneJob');
+    $clone_mock->redefine(_handle_unexpected_return_code => sub ($tx) { die 
'unexpected return code' });
+    my $url_handler = {remote => Test::FakeLWPUserAgent->new, remote_url => 
Mojo::URL->new('foo')};
+    throws_ok { clone_job_get_job(42, $url_handler, {'ignore-missing-assets' 
=> 1}) } qr/unexpected return code/,
+      'unexpected return code handled';
+};
+
 subtest 'clone job apply settings tests' => sub {
     my %test_settings = %child_settings;
     $test_settings{HDD_1} = 'new.qcow2';
@@ -456,4 +473,10 @@
     $ua->get('http://foobar/some/path');
 };
 
+subtest 'determining base URL' => sub {
+    is openqa_baseurl(Mojo::URL->new('http://foo:80/bar')), 'http://foo', 
'default http port removed';
+    is openqa_baseurl(Mojo::URL->new('https://foo:443/bar')), 'https://foo', 
'default https port removed';
+    is openqa_baseurl(Mojo::URL->new('http://foo:9526/bar')), 
'http://foo:9526', 'custom port preserved';
+};
+
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/t/39-scheduled_products-table.t 
new/openQA-5.1773427330.0b172206/t/39-scheduled_products-table.t
--- old/openQA-5.1773333964.ffc5eff5/t/39-scheduled_products-table.t    
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/39-scheduled_products-table.t    
2026-03-13 19:42:10.000000000 +0100
@@ -157,4 +157,15 @@
     ok $scheduled_products->find(3), 'scheduled product with jobs still 
present';
 };
 
+subtest 'handling failed job cancellation' => sub {
+    my $jobs_mock = Test::MockModule->new('OpenQA::Schema::ResultSet::Jobs');
+    $jobs_mock->redefine(cancel_by_settings => sub { die "fake error" });
+    $schema->txn_begin;
+    my $jobs = {settings_result => [{DISTRI => 'foo', VERSION => 'bar', BUILD 
=> '42'}]};
+    $scheduled_products_mock->redefine(_generate_jobs => $jobs);
+    my $res = $scheduled_product->_schedule_iso({_OBSOLETE => 1}, 
$signal_guard);
+    like "@{$res->{notes}}", qr/.*failed to cancel.*fake error.*/i, 'note 
added' or always_explain $res;
+    $schema->txn_rollback;
+};
+
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/t/api/10-jobgroups.t 
new/openQA-5.1773427330.0b172206/t/api/10-jobgroups.t
--- old/openQA-5.1773333964.ffc5eff5/t/api/10-jobgroups.t       2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/api/10-jobgroups.t       2026-03-13 
19:42:10.000000000 +0100
@@ -591,5 +591,14 @@
     }
 };
 
+subtest 'helper for removing test suite defaults' => sub {
+    my $helper = 
\&OpenQA::Schema::Result::JobGroups::_remove_test_suite_defaults;
+    my $group = {scenarios => {x86_64 => {product => [{foo => {machine => 
'64bit'}}]}}};
+    my $test_suites = {x86_64 => {foo => 1}};
+    my $scenarios = [];
+    $helper->(qw(product 64bit x86_64), $group, $test_suites, $scenarios);
+    is_deeply $scenarios, ['foo'], 'scenario is added';
+    is $group->{scenarios}->{x86_64}->{product}, $scenarios, 'scenarios are 
assigned to group';
+};
 
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1773333964.ffc5eff5/t/ui/14-dashboard-parents.t 
new/openQA-5.1773427330.0b172206/t/ui/14-dashboard-parents.t
--- old/openQA-5.1773333964.ffc5eff5/t/ui/14-dashboard-parents.t        
2026-03-12 17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/t/ui/14-dashboard-parents.t        
2026-03-13 19:42:10.000000000 +0100
@@ -140,8 +140,10 @@
     isnt scalar @{$driver->find_elements('opensuse test', 'link_text')}, 0, 
"child group 'opensuse test' present'";
 };
 
+my $test_parent = $parent_groups->find({name => 'Test parent'});
+
 subtest 'View grouped by group' => sub {
-    $driver->get('/parent_group_overview/' . $parent_groups->find({name => 
'Test parent'})->id);
+    $driver->get('/parent_group_overview/' . $test_parent->id);
     $driver->find_element_by_id('grouped_by_group_tab')->click();
     is
       
$driver->find_element_by_id('grouped_by_group_tab')->get_attribute('class'),
@@ -154,5 +156,11 @@
     $driver->find_element_by_id('grouped_by_group')->is_displayed();
 };
 
+subtest 'rendered description' => sub {
+    $test_parent->update({description => '**test description**'});
+    $driver->get('/parent_group_overview/' . $test_parent->id);
+    is $driver->find_element_by_id('group_description')->get_text, 'test 
description', 'description rendered';
+};
+
 kill_driver();
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1773333964.ffc5eff5/tools/test_helm_chart 
new/openQA-5.1773427330.0b172206/tools/test_helm_chart
--- old/openQA-5.1773333964.ffc5eff5/tools/test_helm_chart      2026-03-12 
17:46:04.000000000 +0100
+++ new/openQA-5.1773427330.0b172206/tools/test_helm_chart      2026-03-13 
19:42:10.000000000 +0100
@@ -6,7 +6,7 @@
 
 cd container/helm
 
-gw_disabled=(--helm-extra-set-args "--set=gateway.enabled=false")
+ct_extra=(--helm-extra-set-args "--set=gateway.enabled=false 
--set=image.pullPolicy=IfNotPresent --set=worker.image.pullPolicy=IfNotPresent")
 ct_install_args=()
-[[ "${1:-lint}" != "lint" ]] && ct_install_args=("${gw_disabled[@]}")
+[[ "${1:-lint}" != "lint" ]] && ct_install_args=("${ct_extra[@]}")
 ct "${1:-lint}" --debug --all --config ct.yaml "${ct_install_args[@]}"

++++++ openQA.obsinfo ++++++
--- /var/tmp/diff_new_pack.rtYZIL/_old  2026-03-14 22:24:01.045162651 +0100
+++ /var/tmp/diff_new_pack.rtYZIL/_new  2026-03-14 22:24:01.057163147 +0100
@@ -1,5 +1,5 @@
 name: openQA
-version: 5.1773333964.ffc5eff5
-mtime: 1773333964
-commit: ffc5eff5a7f50e0a7142d097da0596d744301860
+version: 5.1773427330.0b172206
+mtime: 1773427330
+commit: 0b1722061c0be67691e018c85963d7de770032f8
 

Reply via email to