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-07-12 16:21:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openQA (Old)
 and      /work/SRC/openSUSE:Factory/.openQA.new.1991 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openQA"

Sun Jul 12 16:21:03 2026 rev:866 rq:1364942 version:5.1783672871.e689eb3c

Changes:
--------
--- /work/SRC/openSUSE:Factory/openQA/openQA.changes    2026-07-08 
17:39:57.728635217 +0200
+++ /work/SRC/openSUSE:Factory/.openQA.new.1991/openQA.changes  2026-07-12 
16:21:41.153078479 +0200
@@ -1,0 +2,19 @@
+Fri Jul 10 11:31:25 UTC 2026 - [email protected]
+
+- Update to version 5.1783672871.e689eb3c:
+  * chore(deps): Dependency cron 2026-07-10
+  * fix(test): resolve List::Util::any coverage bug
+  * refactor(test): use List::Util::any in full-stack
+  * feat(scheduler): Clarify job missing class reason
+  * feat(scheduler): distinguish busy vs offline workers
+  * fix(test): wait for processes in full-stack teardown
+  * docs(agents): document make check-line-coverage in AGENTS.md
+  * feat(build): add check-line-coverage target
+  * chore(deps): Dependency cron 2026-07-09
+  * ci: Add cli tool to report uncovered lines
+  * feat: allow UEFI_PFLASH_CODE as asset
+  * refactor: Remove duplicate 'use' statement
+  * fix(client): Encode text_data as UTF-8 in archive command
+  * fix: Handle source Urls ending with .git
+
+-------------------------------------------------------------------

Old:
----
  openQA-5.1783486111.5733fbdf.obscpio

New:
----
  openQA-5.1783672871.e689eb3c.obscpio

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

Other differences:
------------------
++++++ openQA-client-test.spec ++++++
--- /var/tmp/diff_new_pack.5NdW9T/_old  2026-07-12 16:21:44.625195507 +0200
+++ /var/tmp/diff_new_pack.5NdW9T/_new  2026-07-12 16:21:44.649196316 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-client
 Name:           %{short_name}-test
-Version:        5.1783486111.5733fbdf
+Version:        5.1783672871.e689eb3c
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-devel-test.spec ++++++
--- /var/tmp/diff_new_pack.5NdW9T/_old  2026-07-12 16:21:45.017208720 +0200
+++ /var/tmp/diff_new_pack.5NdW9T/_new  2026-07-12 16:21:45.021208854 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-devel
 Name:           %{short_name}-test
-Version:        5.1783486111.5733fbdf
+Version:        5.1783672871.e689eb3c
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-test.spec ++++++
--- /var/tmp/diff_new_pack.5NdW9T/_old  2026-07-12 16:21:45.261216944 +0200
+++ /var/tmp/diff_new_pack.5NdW9T/_new  2026-07-12 16:21:45.277217483 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA
 Name:           %{short_name}-test
-Version:        5.1783486111.5733fbdf
+Version:        5.1783672871.e689eb3c
 Release:        0
 Summary:        Test package for openQA
 License:        GPL-2.0-or-later

++++++ openQA-worker-test.spec ++++++
--- /var/tmp/diff_new_pack.5NdW9T/_old  2026-07-12 16:21:45.553226786 +0200
+++ /var/tmp/diff_new_pack.5NdW9T/_new  2026-07-12 16:21:45.577227595 +0200
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-worker
 Name:           %{short_name}-test
-Version:        5.1783486111.5733fbdf
+Version:        5.1783672871.e689eb3c
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA.spec ++++++
--- /var/tmp/diff_new_pack.5NdW9T/_old  2026-07-12 16:21:45.937239730 +0200
+++ /var/tmp/diff_new_pack.5NdW9T/_new  2026-07-12 16:21:45.945240000 +0200
@@ -104,7 +104,7 @@
 %define devel_requires %devel_no_selenium_requires chromedriver
 
 Name:           openQA
-Version:        5.1783486111.5733fbdf
+Version:        5.1783672871.e689eb3c
 Release:        0
 Summary:        Framework for automated system-level testing (web-frontend, 
scheduler and tools)
 Group:          Development/Tools/Other

++++++ openQA-5.1783486111.5733fbdf.obscpio -> 
openQA-5.1783672871.e689eb3c.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/.circleci/config.yml 
new/openQA-5.1783672871.e689eb3c/.circleci/config.yml
--- old/openQA-5.1783486111.5733fbdf/.circleci/config.yml       2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/.circleci/config.yml       2026-07-10 
10:41:11.000000000 +0200
@@ -207,6 +207,7 @@
       - codecov/upload:
           files: cover_db/codecov.json
           verbose: true
+      - run: ./tools/check-line-coverage cover_db/codecov.json
 
   build-docs: &docs-template
     docker:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/AGENTS.md 
new/openQA-5.1783672871.e689eb3c/AGENTS.md
--- old/openQA-5.1783486111.5733fbdf/AGENTS.md  2026-07-08 06:48:31.000000000 
+0200
+++ new/openQA-5.1783672871.e689eb3c/AGENTS.md  2026-07-10 10:41:11.000000000 
+0200
@@ -11,6 +11,7 @@
 - `make test-unit-and-integration TESTS=t/your_test.t`: Run specific tests.
 - `COVERAGE=1 make test-unit-and-integration TESTS=t/your_test.t`: Run
   specific tests with statement coverage enabled.
+- `make check-line-coverage`: Check for uncovered lines in current cover_db.
 - `cover -report text -select_re 'path/to/modified_file'`: Generate and view a
   fast text-based coverage report restricted to the specific file you changed
   (run this after generating the coverage database).
@@ -34,9 +35,9 @@
   creating git commits.
 - **Strict Statement Coverage:** Full statement coverage is a hard
   requirement. You MUST run tests with `COVERAGE=1` for your specific test
-  adaptations and verify that all new/modified lines are covered using `cover
-  -report text` before creating git commits. Do not wait for CI/Codecov in PRs
-  to catch missing coverage.
+  adaptations, and then run `make check-line-coverage` to verify that all
+  new/modified lines are covered. Do not wait for CI/Codecov in PRs to catch
+  missing coverage.
 
 ## Constraints
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/Makefile 
new/openQA-5.1783672871.e689eb3c/Makefile
--- old/openQA-5.1783486111.5733fbdf/Makefile   2026-07-08 06:48:31.000000000 
+0200
+++ new/openQA-5.1783672871.e689eb3c/Makefile   2026-07-10 10:41:11.000000000 
+0200
@@ -386,6 +386,10 @@
        export DEVEL_COVER_DB_FORMAT=JSON;\
        cover $(COVER_REPORT_OPTS) -report codecovbash
 
+.PHONY: check-line-coverage
+check-line-coverage: coverage-report-codecov ## Check for uncovered lines in 
current cover_db
+       ./tools/check-line-coverage cover_db/codecov.json
+
 .PHONY: coverage-codecov
 coverage-codecov: coverage ## Run coverage and generate codecov report
        $(MAKE) coverage-report-codecov
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1783486111.5733fbdf/assets/javascripts/test_result.js 
new/openQA-5.1783672871.e689eb3c/assets/javascripts/test_result.js
--- old/openQA-5.1783486111.5733fbdf/assets/javascripts/test_result.js  
2026-07-08 06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/assets/javascripts/test_result.js  
2026-07-10 10:41:11.000000000 +0200
@@ -641,7 +641,7 @@
       for (const line of lines) {
         const found = line.match(testGitInfoRe);
         if (found) {
-          logFileElement.dataset.gitBaseUrl = found[2] + '/blob/' + found[1];
+          logFileElement.dataset.gitBaseUrl = found[2].replace(/\.git$/, '') + 
'/blob/' + found[1];
           break;
         }
       }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/docs/UsersGuide.md 
new/openQA-5.1783672871.e689eb3c/docs/UsersGuide.md
--- old/openQA-5.1783486111.5733fbdf/docs/UsersGuide.md 2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/docs/UsersGuide.md 2026-07-10 
10:41:11.000000000 +0200
@@ -1468,6 +1468,7 @@
 - `ISO` (type `iso`)
 - `ISO_n` (type `iso`)
 - `HDD_n` (type `hdd`)
+- `UEFI_PFLASH_CODE` (type `hdd`) (in some cases, see below)
 - `UEFI_PFLASH_VARS` (type `hdd`) (in some cases, see below)
 - `REPO_n` (type `repo`)
 - `ASSET_n` (type `other`)
@@ -1485,7 +1486,8 @@
 [asset cleanup](UsersGuide.md#asset_cleanup).
 Non-fixed assets are always subject to the cleanup.
 
-`UEFI_PFLASH_VARS` is a special case: whether it is treated as an asset 
depends on the value. If the value looks like an absolute path (starts with 
`/`), it will not be treated as an asset (and
+`UEFI_PFLASH_CODE` and `_VARS` are a special case: whether it is treated as an 
asset depends on the value.
+If the value looks like an absolute path (starts with `/`), it will not be 
treated as an asset (and
 so the value should be an absolute path for a file which exists on the 
relevant worker system(s)).
 Otherwise, it is treated as an `hdd`-type asset. This allows tests to use a 
stock base image
 (like the ones provided by edk2) for a simple case, but also allows a job to 
upload its image on
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Client/Archive.pm 
new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Client/Archive.pm
--- old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Client/Archive.pm       
2026-07-08 06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Client/Archive.pm       
2026-07-10 10:41:11.000000000 +0200
@@ -63,7 +63,7 @@
     }
     elsif ($module->{text}) {
         my $file = $path->child('testresults', $module->{text});
-        $file->spew($module->{text_data} // "No data\n");
+        $file->spew($module->{text_data} // "No data\n", 'UTF-8');
     }
     return undef;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Scheduler/Model/Jobs.pm 
new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Scheduler/Model/Jobs.pm
--- old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Scheduler/Model/Jobs.pm 
2026-07-08 06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Scheduler/Model/Jobs.pm 
2026-07-10 10:41:11.000000000 +0200
@@ -29,16 +29,19 @@
 has shuffle_workers => 1;
 has dynamic_limit => sub { OpenQA::Scheduler::DynamicLimit->new };
 
-sub determine_free_workers ($shuffle = 0) {
-    my @free_workers = grep { !$_->dead } 
OpenQA::Schema->singleton->resultset('Workers')->search(
+sub determine_online_workers ($shuffle = 0) {
+    my @online_workers = grep { !$_->dead } 
OpenQA::Schema->singleton->resultset('Workers')->search(
         {
-            job_id => undef,
             error => undef,
             'properties.key' => 'WEBSOCKET_API_VERSION',
             'properties.value' => WEBSOCKET_API_VERSION
         },
         {join => 'properties'})->all;
-    return $shuffle ? shuffle(\@free_workers) : \@free_workers;
+    return $shuffle ? shuffle(\@online_workers) : \@online_workers;
+}
+
+sub determine_free_workers ($online_workers) {
+    return [grep { !$_->job_id } @$online_workers];
 }
 
 sub determine_scheduled_jobs ($self) {
@@ -52,7 +55,9 @@
     return $self->dynamic_limit->current_limit($config);
 }
 
-sub _allocate_jobs ($self, $free_workers) {
+sub _allocate_jobs ($self, $online_workers = undef, $free_workers = undef) {
+    $online_workers //= determine_online_workers($self->shuffle_workers);
+    $free_workers //= determine_free_workers($online_workers);
     my ($allocated_workers, $allocated_jobs) = ({}, {});
     my $scheduled_jobs = $self->scheduled_jobs;
     $scheduled_jobs->{$_}->{current_reason} = undef for keys %$scheduled_jobs;
@@ -76,10 +81,14 @@
     my %rejected;
     for my $id (keys %$scheduled_jobs) {
         my $jobinfo = $scheduled_jobs->{$id};
-        $jobinfo->{matching_workers} = _matching_workers($jobinfo, 
$free_workers, \%rejected);
+        my $has_matching_online = 0;
+        $jobinfo->{matching_workers} = _matching_workers($jobinfo, 
$online_workers, \%rejected, \$has_matching_online);
         if (!@{$jobinfo->{matching_workers}}) {
             my @needed = sort @{$jobinfo->{worker_classes}};
-            $jobinfo->{current_reason} = @needed ? "no free workers for class 
@needed" : 'no workers online';
+            $jobinfo->{current_reason}
+              = !@needed ? 'job has no worker class'
+              : $has_matching_online ? "no free workers for class @needed"
+              : "no workers online for requested class @needed at all";
         }
     }
     if (keys %rejected) {
@@ -218,7 +227,8 @@
 sub schedule ($self) {
     my $start_time = time;
     my $schema = OpenQA::Schema->singleton;
-    my $free_workers = determine_free_workers($self->shuffle_workers);
+    my $online_workers = determine_online_workers($self->shuffle_workers);
+    my $free_workers = determine_free_workers($online_workers);
     my $worker_count = $schema->resultset('Workers')->count;
     my $free_worker_count = @$free_workers;
 
@@ -226,7 +236,7 @@
     log_debug("Scheduling: Free workers: $free_worker_count/$worker_count; 
Scheduled jobs: "
           . (scalar keys %$scheduled_jobs));
 
-    my ($allocated_workers, $allocated_jobs) = 
$self->_allocate_jobs($free_workers);
+    my ($allocated_workers, $allocated_jobs) = 
$self->_allocate_jobs($online_workers, $free_workers);
 
     $self->_update_job_reasons_in_db($schema, $scheduled_jobs);
     my @successfully_allocated;
@@ -374,15 +384,16 @@
 
 sub singleton ($class = undef) { state $jobs ||= ($class // __PACKAGE__)->new }
 
-sub _matching_workers ($jobinfo, $free_workers, $rejected = {}) {
-    my @filtered;
+sub _matching_workers ($jobinfo, $online_workers, $rejected = {}, 
$has_matching_online_ref = undef) {
+    my @free_matching;
     my @needed = sort @{$jobinfo->{worker_classes}};
-    for my $worker (@$free_workers) {
-        my $matched_all = all { $worker->check_class($_) } @needed;
-        push @filtered, $worker if $matched_all;
+    for my $worker (@$online_workers) {
+        next unless all { $worker->check_class($_) } @needed;
+        $$has_matching_online_ref = 1 if $has_matching_online_ref;
+        push @free_matching, $worker unless $worker->job_id;
     }
-    $rejected->{join ',', @needed}++ unless @filtered;
-    return \@filtered;
+    $rejected->{join ',', @needed}++ unless @free_matching;
+    return \@free_matching;
 }
 
 sub _jobs_in_execution ($need) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Schema.pm 
new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Schema.pm
--- old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Schema.pm       2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Schema.pm       2026-07-10 
10:41:11.000000000 +0200
@@ -18,7 +18,6 @@
 use OpenQA::Config;
 use OpenQA::Utils qw(:DEFAULT prjdir);
 use Mojo::File qw(path);
-use Feature::Compat::Try;
 use POSIX qw(setuid setgid);
 
 # after bumping the version please look at the instructions in the 
docs/Contributing.md file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Utils.pm 
new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Utils.pm
--- old/openQA-5.1783486111.5733fbdf/lib/OpenQA/Utils.pm        2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/lib/OpenQA/Utils.pm        2026-07-10 
10:41:11.000000000 +0200
@@ -379,12 +379,12 @@
 }
 
 # passing $value is optional but makes the result more accurate
-# (it affects only UEFI_PFLASH_VARS currently).
+# (it affects only UEFI_PFLASH_(CODE|VARS) currently).
 sub asset_type_from_setting ($setting, $value = undef) {
     return 'iso' if $setting eq 'ISO' || $setting =~ /^ISO_\d+$/;
     return 'hdd' if $setting =~ /^HDD_\d+$/;
-    # non-absolute-path value of UEFI_PFLASH_VARS treated as HDD asset
-    return 'hdd' if $setting eq 'UEFI_PFLASH_VARS' && ($value // '') !~ m,^/,;
+    # non-absolute-path value of UEFI_PFLASH_(CODE|VARS) treated as HDD asset
+    return 'hdd' if $setting =~ /^UEFI_PFLASH_(?:CODE|VARS)$/ && ($value // 
'') !~ m{^/};
     return 'repo' if $setting =~ /^REPO_\d+$/;
     return 'other' if $setting =~ /^ASSET_\d+$/ || $setting eq 'KERNEL' || 
$setting eq 'INITRD';
     # empty string if this doesn't look like an asset type
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/t/04-scheduler.t 
new/openQA-5.1783672871.e689eb3c/t/04-scheduler.t
--- old/openQA-5.1783486111.5733fbdf/t/04-scheduler.t   2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/t/04-scheduler.t   2026-07-10 
10:41:11.000000000 +0200
@@ -303,7 +303,8 @@
     my $rollback = scope_guard sub { $schema->txn_rollback };
     my $scheduler_mock = 
Test::MockModule->new('OpenQA::Scheduler::Model::Jobs');
     my $fake_allocated = {worker => 1, job => 1};
-    $scheduler_mock->redefine(_allocate_jobs => sub ($self, $free_workers) { 
({}, {1 => $fake_allocated}) });
+    $scheduler_mock->redefine(
+        _allocate_jobs => sub ($self, $online_workers = undef, $free_workers = 
undef) { ({}, {1 => $fake_allocated}) });
     my $running = $jobs->create({TEST => 'running', state => RUNNING});
     $job_dependencies->create({child_job_id => 1, parent_job_id => 
$running->id, dependency => PARALLEL});
     my $allocated = OpenQA::Scheduler::Model::Jobs->singleton->schedule();
@@ -440,11 +441,11 @@
         my @classes = qw(atari c64 quantum);
         my $scheduler = OpenQA::Scheduler::Model::Jobs->singleton;
         my $scheduled_jobs = $scheduler->determine_scheduled_jobs;
-        my $free_workers = 
OpenQA::Scheduler::Model::Jobs::determine_free_workers();
+        my $online_workers = 
OpenQA::Scheduler::Model::Jobs::determine_online_workers();
         my %rejected;
         for my $jobinfo (values %$scheduled_jobs) {
             $jobinfo->{matching_workers}
-              = OpenQA::Scheduler::Model::Jobs::_matching_workers($jobinfo, 
$free_workers, \%rejected);
+              = OpenQA::Scheduler::Model::Jobs::_matching_workers($jobinfo, 
$online_workers, \%rejected);
         }
         my $expected = {atari => 3, c64 => 3, quantum => 3};
         is_deeply \%rejected, $expected, 'Rejected worker classes statistics 
like expected';
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/t/05-scheduler-full.t 
new/openQA-5.1783672871.e689eb3c/t/05-scheduler-full.t
--- old/openQA-5.1783486111.5733fbdf/t/05-scheduler-full.t      2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/t/05-scheduler-full.t      2026-07-10 
10:41:11.000000000 +0200
@@ -114,7 +114,7 @@
     is @$allocated, 0, 'no jobs allocated for no active workers';
 
     my $jobs = $schema->resultset('Jobs');
-    is $jobs->search({state => SCHEDULED})->first->reason, 'no workers online',
+    is $jobs->search({state => SCHEDULED})->first->reason, 'job has no worker 
class',
       'unallocated job has a scheduling reason in the DB';
 
     note 'starting two workers';
@@ -133,7 +133,7 @@
     always_explain $allocated unless $different_workers && $different_jobs;
 
     note 'verify scheduling reason for unallocated jobs';
-    is $new_job->discard_changes->reason, 'no free workers for class foo',
+    is $new_job->discard_changes->reason, 'no workers online for requested 
class foo at all',
       'unallocated job has a scheduling reason in the DB';
 
     $allocated = $job_model->schedule;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/t/05-scheduler-mocked.t 
new/openQA-5.1783672871.e689eb3c/t/05-scheduler-mocked.t
--- old/openQA-5.1783486111.5733fbdf/t/05-scheduler-mocked.t    2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/t/05-scheduler-mocked.t    2026-07-10 
10:41:11.000000000 +0200
@@ -38,7 +38,7 @@
 my @worker_fields = (host => 'testworker', properties => [{key => 
'WORKER_CLASS', value => 'qemu_x86_64'}]);
 my @mocked_free_workers = map { $workers->create({@worker_fields, instance => 
$_}) } 1 .. 3;
 my $spare_worker = pop @mocked_free_workers;
-$mock->redefine(determine_free_workers => sub { \@mocked_free_workers });
+$mock->redefine(determine_online_workers => sub { \@mocked_free_workers });
 $mock->redefine(determine_scheduled_jobs => sub { 
shift->scheduled_jobs(\%mocked_jobs); \%mocked_jobs });
 
 # prevent writing to a log file to enable use of combined_like in the 
following tests
@@ -81,9 +81,9 @@
     my $second_child_job = $jobs->create({id => 3, state => SCHEDULED, TEST => 
$mocked_jobs{3}->{test}});
 
     # run the scheduler; parallel parent supposed to be prioritized
-    my $free_workers = 
OpenQA::Scheduler::Model::Jobs::determine_free_workers();
+    my $online_workers = 
OpenQA::Scheduler::Model::Jobs::determine_online_workers();
     my $allocated_workers;
-    combined_like { $allocated_workers = 
OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($free_workers) }
+    combined_like { $allocated_workers = 
OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($online_workers) }
     qr/Need to schedule 3 parallel jobs for job 1.*Discarding job 
[123].*Discarding job [123]/s,
       'discarding jobs due to incomplete parallel cluster';
     is $mocked_jobs{1}->{priority_offset}, 10, 'priority of parallel parent 
increased (once per child)';
@@ -94,7 +94,7 @@
 
     # run the scheduler again assuming highest prio for parallel parent; 
worker supposed to be "held"
     $mocked_jobs{1}->{priority} = 0;
-    combined_like { ($allocated_workers) = 
OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($free_workers) }
+    combined_like { ($allocated_workers) = 
OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($online_workers) }
     qr/Holding worker .* for job [123] to avoid starvation.*Holding worker .* 
for job [123] to avoid starvation/s,
       'holding 2 workers (for 2 of our parallel jobs while 3rd worker is 
unavailable)';
     is_deeply [sort keys %$allocated_workers], [map { $_->id } 
@mocked_free_workers], 'both free workers "held"';
@@ -106,13 +106,32 @@
     delete $mocked_jobs{2};
 
     my ($allocated_workers, $allocated_jobs);
-    my $free_workers = 
OpenQA::Scheduler::Model::Jobs::determine_free_workers();
+    my $online_workers = 
OpenQA::Scheduler::Model::Jobs::determine_online_workers();
     combined_like {
-        ($allocated_workers, $allocated_jobs) = 
OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($free_workers)
+        ($allocated_workers, $allocated_jobs)
+          = 
OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($online_workers)
     }
     qr/Skipping job .* because dependent jobs are not ready/, 'skipping job if 
dependent jobs not ready';
     is_deeply $allocated_jobs, {}, 'no jobs allocated' or always_explain 
$allocated_jobs;
     is_deeply $allocated_workers, {}, 'no workers allocated' or always_explain 
$allocated_workers;
 };
 
+subtest 'scheduling reason when workers are busy' => sub {
+    my $job_id = 1;
+    $_->update({job_id => $job_id++}) for @mocked_free_workers;
+    my ($allocated_workers, $allocated_jobs);
+    combined_like {
+        ($allocated_workers, $allocated_jobs) = 
OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs()
+    }
+    qr/Skipping 2 jobs because of no free workers for requested worker 
classes/, 'logging busy workers';
+    is $mocked_jobs{1}->{current_reason}, 'no free workers for class 
qemu_x86_64',
+      'scheduling reason is busy workers when workers are online but not free';
+
+    $_->update({job_id => undef}) for @mocked_free_workers;
+    my $jobinfo = $mocked_jobs{1};
+    my $online_workers = 
OpenQA::Scheduler::Model::Jobs::determine_online_workers();
+    my $temp_matching = 
OpenQA::Scheduler::Model::Jobs::_matching_workers($jobinfo, $online_workers);
+    is_deeply $temp_matching, \@mocked_free_workers, 'matching workers found 
without optional parameters';
+};
+
 done_testing();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/t/16-utils.t 
new/openQA-5.1783672871.e689eb3c/t/16-utils.t
--- old/openQA-5.1783486111.5733fbdf/t/16-utils.t       2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/t/16-utils.t       2026-07-10 
10:41:11.000000000 +0200
@@ -327,6 +327,9 @@
 subtest asset_type_from_setting => sub {
     use OpenQA::Utils 'asset_type_from_setting';
     is asset_type_from_setting('ISO'), 'iso', 'simple from ISO';
+    is asset_type_from_setting('UEFI_PFLASH_CODE'), 'hdd', 'simple from 
UEFI_PFLASH_CODE';
+    is asset_type_from_setting('UEFI_PFLASH_CODE', 'relative'), 'hdd', 
'relative from UEFI_PFLASH_CODE';
+    is asset_type_from_setting('UEFI_PFLASH_CODE', '/absolute'), '', 'absolute 
from UEFI_PFLASH_CODE';
     is asset_type_from_setting('UEFI_PFLASH_VARS'), 'hdd', 'simple from 
UEFI_PFLASH_VARS';
     is asset_type_from_setting('UEFI_PFLASH_VARS', 'relative'), 'hdd', 
'relative from UEFI_PFLASH_VARS';
     is asset_type_from_setting('UEFI_PFLASH_VARS', '/absolute'), '', 'absolute 
from UEFI_PFLASH_VARS';
@@ -371,8 +374,10 @@
     };
     is_deeply $assets, $refassets, 'correct with absolute UEFI_PFLASH_VARS';
     # now make this relative: it should now be seen as an asset type
+    $settings->{UEFI_PFLASH_CODE} = 'uefi_pflash_code.qcow2';
     $settings->{UEFI_PFLASH_VARS} = 'uefi_pflash_vars.qcow2';
     $assets = parse_assets_from_settings($settings);
+    $refassets->{UEFI_PFLASH_CODE} = {type => 'hdd', name => 
'uefi_pflash_code.qcow2'};
     $refassets->{UEFI_PFLASH_VARS} = {type => 'hdd', name => 
'uefi_pflash_vars.qcow2'};
     is_deeply $assets, $refassets, 'correct with relative UEFI_PFLASH_VARS';
     # KERNEL/INITRD are treated similar to UEFI_PFLASH_VARS
@@ -380,7 +385,6 @@
     $assets = parse_assets_from_settings($settings);
     $refassets->{KERNEL} = {type => 'other', name => '/path/vmlinuz'};
     is_deeply $assets, $refassets, 'correct with absolute KERNEL';
-
 };
 
 subtest 'base_host' => sub {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1783486111.5733fbdf/t/43-scheduling-and-worker-scalability.t 
new/openQA-5.1783672871.e689eb3c/t/43-scheduling-and-worker-scalability.t
--- old/openQA-5.1783486111.5733fbdf/t/43-scheduling-and-worker-scalability.t   
2026-07-08 06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/t/43-scheduling-and-worker-scalability.t   
2026-07-10 10:41:11.000000000 +0200
@@ -190,7 +190,9 @@
 
             # ensure the scheduler also sees them as free
             for my $try (1 .. $polling_tries_workers) {
-                last if scalar 
@{OpenQA::Scheduler::Model::Jobs::determine_free_workers()} == $worker_count;
+                last
+                  if scalar @{[grep { !$_->job_id } 
@{OpenQA::Scheduler::Model::Jobs::determine_online_workers()}]}
+                  == $worker_count;
                 sleep $polling_interval;    # uncoverable statement
             }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/t/full-stack.t 
new/openQA-5.1783672871.e689eb3c/t/full-stack.t
--- old/openQA-5.1783486111.5733fbdf/t/full-stack.t     2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/t/full-stack.t     2026-07-10 
10:41:11.000000000 +0200
@@ -66,7 +66,10 @@
 my $livehandler;
 
 sub turn_down_stack {
-    stop_service($_) for ($worker, $ws, $livehandler);
+    for my $service (grep { defined } $worker, $ws, $livehandler) {
+        stop_service($service);
+        $service->finish;
+    }
 }
 sub stop_worker { stop_service $worker }
 
@@ -118,8 +121,9 @@
     #       properties (most importantly WEBSOCKET_API_VERSION and 
WORKER_CLASS) have not been populated yet.
     my ($elapsed, $free_workers) = (0, []);
     for (; $elapsed <= $setup_timeout; $elapsed += sleep $setup_poll_interval) 
{
-        for my $worker (@{$free_workers = 
OpenQA::Scheduler::Model::Jobs::determine_free_workers}) {
-            next unless $worker->check_class($worker_class);
+        $free_workers = OpenQA::Scheduler::Model::Jobs::determine_free_workers(
+            OpenQA::Scheduler::Model::Jobs::determine_online_workers());
+        if (my @matching = grep { $_->check_class($worker_class) } 
@$free_workers) {
             pass "at least one free worker with class $worker_class 
registered";
             return ($relevant_jobs, $elapsed);
         }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1783486111.5733fbdf/t/testresults/00099/00099946-opensuse-13.1-DVD-i586-Build0091-textmode/autoinst-log.txt
 
new/openQA-5.1783672871.e689eb3c/t/testresults/00099/00099946-opensuse-13.1-DVD-i586-Build0091-textmode/autoinst-log.txt
--- 
old/openQA-5.1783486111.5733fbdf/t/testresults/00099/00099946-opensuse-13.1-DVD-i586-Build0091-textmode/autoinst-log.txt
    2026-07-08 06:48:31.000000000 +0200
+++ 
new/openQA-5.1783672871.e689eb3c/t/testresults/00099/00099946-opensuse-13.1-DVD-i586-Build0091-textmode/autoinst-log.txt
    2026-07-10 10:41:11.000000000 +0200
@@ -1281,7 +1281,7 @@
  waiting for console read thread to quit...
 WARNING: read qmp 145 - {"timestamp": {"seconds": 1392728646, "microseconds": 
936768}, "event": "DEVICE_TRAY_MOVED", "data": {"device": "ide1-cd0", 
"tray-open": true}}
 
-[2026-06-22T20:28:39.215621+02:00] [debug] [pid:14231] TEST_GIT_HASH=c0ffee 
TEST_GIT_URL=https://example.org/fortest
+[2026-06-22T20:28:39.215621+02:00] [debug] [pid:14231] TEST_GIT_HASH=c0ffee 
TEST_GIT_URL=https://example.org/fortest.git
 [2026-06-22T20:29:10.379120+02:00] [debug] [pid:14264] 
[step:installation,bootloader,1] tests/installaton/bootloader.pm:5 called 
utils::wait_for_desktop -> lib/utils.pm:47 called utils::handle_gui_password
 [2026-06-22T20:29:10.379120+02:00] [debug] [pid:14264] 
[step:installation,bootloader,2] tests/installaton/bootloader.pm:5 called 
utils::wait_for_desktop -> lib/utils.pm:47 called utils::handle_gui_password
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1783486111.5733fbdf/tools/check-line-coverage 
new/openQA-5.1783672871.e689eb3c/tools/check-line-coverage
--- old/openQA-5.1783486111.5733fbdf/tools/check-line-coverage  1970-01-01 
01:00:00.000000000 +0100
+++ new/openQA-5.1783672871.e689eb3c/tools/check-line-coverage  2026-07-10 
10:41:11.000000000 +0200
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+input_file=$1
+limit=${2:-500}
+
+declare -i counter=0
+
+echo "Checking for uncovered lines" >&2
+
+while read -r file line; do
+    [[ $line -eq 0 ]] && continue
+    if [[ -f "$file" ]]; then
+        content=$(sed -n "${line}p" "$file")
+        printf "%-60s %s\n" "$file:$line" "| $content"
+        counter+=1
+        if [[ $counter -ge $limit ]]; then
+            echo "Reached limit of $limit printed lines. Stopping." >&2
+            break
+        fi
+    else
+        echo "Warning: File '$file' not found." >&2
+    fi
+done < <(jq -r '.coverage | to_entries[] | .key as $file | .value | 
to_entries[] | select(.value == 0) | "\($file) \(.key)"' "$input_file")
+
+if [[ $counter -gt 0 ]]; then
+    echo "$counter uncovered lines" >&2
+    exit 1
+fi
+
+echo "All lines covered \\o/" >&2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1783486111.5733fbdf/tools/ci/autoinst.sha 
new/openQA-5.1783672871.e689eb3c/tools/ci/autoinst.sha
--- old/openQA-5.1783486111.5733fbdf/tools/ci/autoinst.sha      2026-07-08 
06:48:31.000000000 +0200
+++ new/openQA-5.1783672871.e689eb3c/tools/ci/autoinst.sha      2026-07-10 
10:41:11.000000000 +0200
@@ -1 +1 @@
-ad31d328dcfe42082b3805e7d883e56296d687e5
\ No newline at end of file
+6ea6c0bea9246eda41d4d83e19433aa04aef704f
\ No newline at end of file

++++++ openQA.obsinfo ++++++
--- /var/tmp/diff_new_pack.5NdW9T/_old  2026-07-12 16:21:59.657702184 +0200
+++ /var/tmp/diff_new_pack.5NdW9T/_new  2026-07-12 16:21:59.669702588 +0200
@@ -1,5 +1,5 @@
 name: openQA
-version: 5.1783486111.5733fbdf
-mtime: 1783486111
-commit: 5733fbdf1f5b1b7a3dae7c428aa74651ad1750ea
+version: 5.1783672871.e689eb3c
+mtime: 1783672871
+commit: e689eb3cd5e8081ff544e1bf54222e999605513c
 

Reply via email to