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
