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-02 17:41:51
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openQA (Old)
 and      /work/SRC/openSUSE:Factory/.openQA.new.29461 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openQA"

Mon Mar  2 17:41:51 2026 rev:814 rq:1335846 version:5.1772460208.7a4e1e06

Changes:
--------
--- /work/SRC/openSUSE:Factory/openQA/openQA.changes    2026-02-27 
17:11:32.336169223 +0100
+++ /work/SRC/openSUSE:Factory/.openQA.new.29461/openQA.changes 2026-03-02 
17:42:11.455988318 +0100
@@ -1,0 +2,24 @@
+Mon Mar 02 14:07:44 UTC 2026 - [email protected]
+
+- Update to version 5.1772460208.7a4e1e06:
+  * docs: Document array-like job settings and `job_setting` parameter
+  * test: Ensure test of filter params of jobs API fails if code breaks
+  * feat: Support searching by job settings in API to list jobs
+  * refactor: Improve `cancel_by_settings`
+  * fix: Allow filtering by more than one job setting in various routes
+  * test: Improve checks in `t/api/02-iso.t`
+  * feat: Allow searching by job settings via overview routes
+  * style: use consistent q{} syntax for SQL strings in Cache Model
+  * refactor: streamline IPC::Run usage and signal handling
+  * test: remove t/25-cache-service.t from unstable_tests.txt
+  * test: improve robustness of t/25-cache-service.t
+  * test: refactor InfluxDB subtest to reduce duplication
+  * test: improve infrastructure for t/25-cache-service.t
+  * fix: improve database robustness in Cache model
+  * fix: log rsync stderr in CacheService::Task::Sync
+  * test: support OPENQA_TEST_WAIT_INTERVAL in wait_for
+  * fix(cache): capture stderr and handle exit status robustly in Sync task
+  * test: make SIGCHLD handler selective in OpenQA::Test::Utils
+  * docs: document aggregate result badges for overview queries
+
+-------------------------------------------------------------------

Old:
----
  openQA-5.1772092969.74a39650.obscpio

New:
----
  openQA-5.1772460208.7a4e1e06.obscpio

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

Other differences:
------------------
++++++ openQA-client-test.spec ++++++
--- /var/tmp/diff_new_pack.rt0fYV/_old  2026-03-02 17:42:12.704040395 +0100
+++ /var/tmp/diff_new_pack.rt0fYV/_new  2026-03-02 17:42:12.708040562 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-client
 Name:           %{short_name}-test
-Version:        5.1772092969.74a39650
+Version:        5.1772460208.7a4e1e06
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-devel-test.spec ++++++
--- /var/tmp/diff_new_pack.rt0fYV/_old  2026-03-02 17:42:12.744042064 +0100
+++ /var/tmp/diff_new_pack.rt0fYV/_new  2026-03-02 17:42:12.748042231 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-devel
 Name:           %{short_name}-test
-Version:        5.1772092969.74a39650
+Version:        5.1772460208.7a4e1e06
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-test.spec ++++++
--- /var/tmp/diff_new_pack.rt0fYV/_old  2026-03-02 17:42:12.776043399 +0100
+++ /var/tmp/diff_new_pack.rt0fYV/_new  2026-03-02 17:42:12.780043566 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA
 Name:           %{short_name}-test
-Version:        5.1772092969.74a39650
+Version:        5.1772460208.7a4e1e06
 Release:        0
 Summary:        Test package for openQA
 License:        GPL-2.0-or-later

++++++ openQA-worker-test.spec ++++++
--- /var/tmp/diff_new_pack.rt0fYV/_old  2026-03-02 17:42:12.816045068 +0100
+++ /var/tmp/diff_new_pack.rt0fYV/_new  2026-03-02 17:42:12.824045402 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-worker
 Name:           %{short_name}-test
-Version:        5.1772092969.74a39650
+Version:        5.1772460208.7a4e1e06
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA.spec ++++++
--- /var/tmp/diff_new_pack.rt0fYV/_old  2026-03-02 17:42:12.900048573 +0100
+++ /var/tmp/diff_new_pack.rt0fYV/_new  2026-03-02 17:42:12.904048740 +0100
@@ -99,7 +99,7 @@
 %define devel_requires %devel_no_selenium_requires chromedriver
 
 Name:           openQA
-Version:        5.1772092969.74a39650
+Version:        5.1772460208.7a4e1e06
 Release:        0
 Summary:        The openQA web-frontend, scheduler and tools
 License:        GPL-2.0-or-later

++++++ openQA-5.1772092969.74a39650.obscpio -> 
openQA-5.1772460208.7a4e1e06.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/docs/UsersGuide.asciidoc 
new/openQA-5.1772460208.7a4e1e06/docs/UsersGuide.asciidoc
--- old/openQA-5.1772092969.74a39650/docs/UsersGuide.asciidoc   2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/docs/UsersGuide.asciidoc   2026-03-02 
15:03:28.000000000 +0100
@@ -573,18 +573,26 @@
 
 === Test result badges https://github.com/os-autoinst/openQA/pull/5022[gh#5022]
 
-For each job result including the latest job result page, there is a 
corresponding
-route to get an SVG status badge that can eg. be used to build a status 
dashboard
-or for showing the status within a GitHub comment.
+For build results or individual job results including the latest job result
+page, there is a corresponding route to get an SVG status badge that can e.g.
+be used to build a status dashboard or for showing the status within a GitHub
+comment.
 
 image::images/badges.png[Test result badges]
 
 ....
 http://openqa/tests/123/badge
 http://openqa/tests/latest/badge
+http://openqa/tests/overview/badge?distri=opensuse&groupid=1&result=not_ok
 ....
 
-There is an optional parameter 'show_build=1' that will
+The first two routes refer to a specific job or the latest job of a scenario.
+The `/tests/overview/badge` route summarizes the status of multiple jobs based
+on overview query parameters, in this example reflecting the most severe
+status. It supports the same query parameters as the overview page for example
+to filter out states taken into consideration.
+
+There is an optional parameter 'show_build=1' for single job badges that will
 prefix the status with the build number.
 
 === Build a continuous openQA monitoring dashboard
@@ -1087,6 +1095,31 @@
 openQA configuration sets `job_settings_max_recent_jobs` which limits
 the results.
 
+==== Array-like job settings
+Settings where the key ends with `[]` are split on `,`. So e.g.
+`ISSUES[]=foo,bar,baz` will be stored as `ISSUES[]=foo`, `ISSUES[]=bar` and
+`ISSUES[]=baz`. This allows searching for jobs with a certain job setting value
+more efficiently, e.g.:
+
+[source,sh]
+----
+openqa-cli api job_settings/jobs key=ISSUES value=foo
+----
+
+Note that the suffix `[]` is still part of the setting key and always needs to
+be specified when referring to the setting. That also means that e.g. `ISSUES`
+and `ISSUES[]` are two completely different settings.
+
+==== Using other API routes
+It is also possible to use the `jobs` and `jobs/overview` routes which support
+the `job_setting` parameter. More than one `job_setting` parameter can be
+specified and it can also be combined with other filtering parameters, e.g.:
+
+[source,sh]
+----
+openqa-cli api jobs result=none job_setting=ISSUES[]={foo,bar} limit=50
+----
+
 === Triggering tests
 
 Tests can be triggered over multiple ways, using `openqa-clone-job`,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/lib/OpenQA/CacheService/Model/Cache.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/CacheService/Model/Cache.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/CacheService/Model/Cache.pm     
2026-02-26 09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/CacheService/Model/Cache.pm     
2026-03-02 15:03:28.000000000 +0100
@@ -22,7 +22,7 @@
 has [qw(location log sqlite min_free_percentage)];
 has limit => 50 * (1024**3);
 
-sub _perform_integrity_check ($self) { $self->sqlite->db->query('pragma 
integrity_check')->arrays->flatten->to_array }
+sub _perform_integrity_check ($self) { $self->sqlite->db->query(q{pragma 
integrity_check})->arrays->flatten->to_array }
 
 sub _check_database_integrity ($self) {
     my $integrity_errors = $self->_perform_integrity_check;
@@ -162,8 +162,8 @@
     try {
         my $db = $self->sqlite->db;
         my $tx = $db->begin('exclusive');
-        my $sql = "INSERT INTO assets (filename, size, last_use) VALUES (?, 0, 
strftime('%s','now'))"
-          . 'ON CONFLICT (filename) DO UPDATE SET pending=1';
+        my $sql = q{INSERT INTO assets (filename, size, last_use) VALUES (?, 
0, strftime('%s','now')) }
+          . q{ON CONFLICT (filename) DO UPDATE SET pending=1, 
last_use=strftime('%s','now')};
         $db->query($sql, $asset);
         $tx->commit;
     }
@@ -171,7 +171,7 @@
 }
 
 sub metrics ($self) {
-    return {map { $_->{name} => $_->{value} } $self->sqlite->db->query('SELECT 
* FROM metrics')->hashes->each};
+    return {map { $_->{name} => $_->{value} } 
$self->sqlite->db->query(q{SELECT * FROM metrics})->hashes->each};
 }
 
 sub _exclusive_query ($self, $sql, @args) {
@@ -182,13 +182,13 @@
 }
 
 sub _update_metric ($self, $name, $value) {
-    $self->_exclusive_query('INSERT INTO metrics (name, value) VALUES ($1, $2) 
ON CONFLICT DO UPDATE SET value = $2',
+    $self->_exclusive_query(q{INSERT INTO metrics (name, value) VALUES ($1, 
$2) ON CONFLICT DO UPDATE SET value = $2},
         $name, $value);
 }
 
 sub _increase_metric ($self, $name, $by_value) {
     $self->_exclusive_query(
-        'INSERT INTO metrics (name, value) VALUES ($1, $2) ON CONFLICT DO 
UPDATE SET value = value + $2',
+        q{INSERT INTO metrics (name, value) VALUES ($1, $2) ON CONFLICT DO 
UPDATE SET value = value + $2},
         $name, $by_value);
 }
 
@@ -197,7 +197,7 @@
 sub _update_asset_last_use ($self, $asset) {
     my $db = $self->sqlite->db;
     my $tx = $db->begin('exclusive');
-    my $sql = "UPDATE assets set last_use = strftime('%s','now'), pending = 0 
where filename = ?";
+    my $sql = q{UPDATE assets set last_use = strftime('%s','now'), pending = 0 
where filename = ?};
     $db->query($sql, $asset);
     $tx->commit;
 
@@ -208,8 +208,9 @@
     my $log = $self->log;
     my $db = $self->sqlite->db;
     my $tx = $db->begin('exclusive');
-    my $sql = "UPDATE assets set etag = ?, size = ?, last_use = 
strftime('%s','now'), pending = 0 where filename = ?";
-    $db->query($sql, $etag, $size, $asset);
+    my $sql = q{INSERT INTO assets (filename, etag, size, last_use, pending) 
VALUES (?, ?, ?, strftime('%s','now'), 0) }
+      . q{ON CONFLICT (filename) DO UPDATE SET etag=excluded.etag, 
size=excluded.size, last_use=excluded.last_use, pending=0};
+    $db->query($sql, $asset, $etag, $size);
     $tx->commit;
 
     my $asset_size = human_readable_size($size);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/lib/OpenQA/CacheService/Task/Sync.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/CacheService/Task/Sync.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/CacheService/Task/Sync.pm       
2026-02-26 09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/CacheService/Task/Sync.pm       
2026-03-02 15:03:28.000000000 +0100
@@ -37,13 +37,12 @@
 
     my @cmd = (qw(rsync -avHP --timeout), RSYNC_TIMEOUT, "$from/", 
qw(--delete), "$to/tests/");
     my $cmd = join ' ', @cmd;
-    $ctx->info("Calling: $cmd");
     my $status;
     my $full_output = '';
     for my $retry (1 .. RSYNC_RETRIES) {
-        my $output = `@cmd`;
-        $status = $? >> 8;
-        $full_output .= "Try $retry:\n" . $output . "\n";
+        my $res = OpenQA::Utils::run_cmd_with_log_return_error(\@cmd);
+        $status = $res->{exit_status};
+        $full_output .= "Try 
$retry:\nSTDOUT:\n$res->{stdout}\nSTDERR:\n$res->{stderr}\n";
         last unless $status;
         sleep RSYNC_RETRY_PERIOD;
     }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/lib/OpenQA/Schema/ResultSet/JobSettings.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/Schema/ResultSet/JobSettings.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/Schema/ResultSet/JobSettings.pm 
2026-02-26 09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/Schema/ResultSet/JobSettings.pm 
2026-03-02 15:03:28.000000000 +0100
@@ -57,34 +57,18 @@
     return \@jobs;
 }
 
-=head2 query_for_settings
-
-=over
-
-=item Return value: ResultSet (to be used as subquery)
-
-=back
-
-Given a perl hash, will create a ResultSet of job_settings
+sub _cond_for_setting ($key, $value) {
+    {'me.key' => $key, 'me.value' => ($value =~ /^:\w+:/) ? {like => "$&%"} : 
$value};
+}
 
-=cut
+sub query_for_setting ($self, $key, $value) {
+    $value
+      ? ({'me.id' => {-in => $self->search(_cond_for_setting($key, 
$value))->get_column('job_id')->as_query}})
+      : ();
+}
 
-sub query_for_settings ($self, $args) {
-    my @conds;
-    # Search into the following job_settings
-    for my $setting (keys %$args) {
-        next unless $args->{$setting};
-        # for dynamic self joins we need to be creative ;(
-        my $tname = 'me';
-        my $setting_value = ($args->{$setting} =~ /^:\w+:/) ? {'like', "$&%"} 
: $args->{$setting};
-        push(
-            @conds,
-            {
-                "$tname.key" => $setting,
-                "$tname.value" => $setting_value
-            });
-    }
-    return $self->search({-and => \@conds});
+sub conds_for_settings ($self, $settings) {
+    {-and => [map { $self->query_for_setting($_, $settings->{$_}) } keys 
%$settings]};
 }
 
 1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/lib/OpenQA/Schema/ResultSet/Jobs.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/Schema/ResultSet/Jobs.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/Schema/ResultSet/Jobs.pm        
2026-02-26 09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/Schema/ResultSet/Jobs.pm        
2026-03-02 15:03:28.000000000 +0100
@@ -12,6 +12,7 @@
 use OpenQA::App;
 use OpenQA::Jobs::Constants;
 use OpenQA::Constants qw(DEFAULT_MAX_JOB_TIME);
+use OpenQA::Jobs::Constants qw(PENDING_STATES EXECUTION_STATES);
 use OpenQA::Log qw(log_trace log_debug log_info);
 use OpenQA::Schema::Result::Jobs;
 use OpenQA::Schema::Result::JobDependencies;
@@ -269,6 +270,7 @@
 sub _prepare_complex_query_search_args ($self, $args) {
     my @conds;
     my @joins;
+    my $job_settings = $args->{job_settings} // {};
 
     if ($args->{module_re}) {
         my $modules = $self->_search_modules($args->{module_re});
@@ -336,17 +338,11 @@
         push(@conds, -or => \@likes);
     }
     else {
-        my %js_settings;
         # Check if the settings are between the arguments passed via query url
         # they come in lowercase, so make sure $key is lc'ed
         for my $key (qw(ISO HDD_1 WORKER_CLASS)) {
-            $js_settings{$key} = $args->{lc $key} if defined $args->{lc $key};
+            $job_settings->{$key} = $args->{lc $key} if defined $args->{lc 
$key};
         }
-        if (keys %js_settings) {
-            my $subquery = 
$schema->resultset('JobSettings')->query_for_settings(\%js_settings);
-            push(@conds, {'me.id' => {-in => 
$subquery->get_column('job_id')->as_query}});
-        }
-
         for my $key (qw(distri version flavor arch test machine)) {
             push(@conds, {'me.' . uc($key) => $args->{$key}}) if $args->{$key};
         }
@@ -355,6 +351,8 @@
         }
     }
 
+    push @conds, 
$schema->resultset('JobSettings')->conds_for_settings($job_settings) if keys 
%$job_settings;
+
     if (defined(my $c = $args->{comment_text})) {
         push @conds, \['(select id from comments where job_id = me.id and text 
like ? limit 1) is not null', "%$c%"];
     }
@@ -428,33 +426,24 @@
   )
 {
     $deprio_limit //= 100;
-    my $rsource = $self->result_source;
-    my $schema = $rsource->schema;
-    # preserve original settings by deep copy
-    my %precond = %{$settings};
-    my %cond;
-
-    for my $key (OpenQA::Schema::Result::Jobs::MAIN_SETTINGS) {
-        $cond{$key} = delete $precond{$key} if defined $precond{$key};
-    }
-    if (keys %precond) {
-        my $subquery = 
$schema->resultset('JobSettings')->query_for_settings(\%precond);
-        $cond{'me.id'} = {-in => $subquery->get_column('job_id')->as_query};
-    }
-    $cond{state} = [OpenQA::Jobs::Constants::PENDING_STATES];
-    my $jobs = $schema->resultset('Jobs')->search(\%cond);
-    my $jobs_to_cancel;
+    my $schema = $self->result_source->schema;
+    my %settings = %$settings;    # make copy to preserve original settings
+    my %main_conds = (
+        state => [PENDING_STATES],
+        map { defined $settings{$_} ? ($_, delete $settings{$_}) : () } 
OpenQA::Schema::Result::Jobs::MAIN_SETTINGS,
+    );
+    my @setting_conds = keys %settings ? 
$schema->resultset('JobSettings')->conds_for_settings(\%settings) : ();
+    my $jobs = $schema->resultset('Jobs')->search({-and => [\%main_conds, 
@setting_conds]});
     if ($newbuild) {
         # filter out all jobs that have any comment (they are considered 
'important') ...
-        $jobs_to_cancel = $jobs->search({'comments.job_id' => undef}, {join => 
'comments'});
+        my $jobs_without_comments = $jobs->search({'comments.job_id' => 
undef}, {join => 'comments'});
         # ... or belong to a tagged build, i.e. is considered important
         # this might be even the tag 'not important' but not much is lost if
         # we still not cancel these builds
-        my $groups_query = $jobs->get_column('group_id')->as_query;
-        my @important_builds = grep defined,
-          map { ($_->tag)[0] } 
$schema->resultset('Comments')->search({'me.group_id' => {-in => 
$groups_query}});
+        my $comments_search = {'me.group_id' => {-in => 
$jobs->get_column('group_id')->as_query}};
+        my @important_builds = map { ($_->tag)[0] // () } 
$schema->resultset('Comments')->search($comments_search);
         my @unimportant_jobs;
-        while (my $j = $jobs_to_cancel->next) {
+        while (my $j = $jobs_without_comments->next) {
             # the value we get from that @important_builds search above
             # could be just BUILD or VERSION-BUILD
             next if grep ($j->BUILD eq $_, @important_builds);
@@ -463,10 +452,7 @@
         }
         # if there are only important jobs there is nothing left for us to do
         return 0 unless @unimportant_jobs;
-        $jobs_to_cancel = $jobs_to_cancel->search({'me.id' => {-in => 
\@unimportant_jobs}});
-    }
-    else {
-        $jobs_to_cancel = $jobs;
+        $jobs = $jobs->search({'me.id' => {-in => \@unimportant_jobs}});
     }
     my $cancelled_jobs = 0;
     my $priority_increment = 10;
@@ -486,11 +472,9 @@
         return $job->cancel($job_result, $reason) // 0;
     };
     # first scheduled to avoid worker grab
-    my $scheduled = $jobs_to_cancel->search({state => SCHEDULED});
-    $cancelled_jobs += $cancel_or_deprioritize->($_) for $scheduled->all;
+    $cancelled_jobs += $cancel_or_deprioritize->($_) for $jobs->search({state 
=> SCHEDULED});
     # then the rest
-    my $executing = $jobs_to_cancel->search({state => 
[OpenQA::Jobs::Constants::EXECUTION_STATES]});
-    $cancelled_jobs += $cancel_or_deprioritize->($_) for $executing->all;
+    $cancelled_jobs += $cancel_or_deprioritize->($_) for $jobs->search({state 
=> [EXECUTION_STATES]});
     OpenQA::App->singleton->emit_event(openqa_job_cancel_by_settings => 
$settings) if $cancelled_jobs;
     return $cancelled_jobs;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1772092969.74a39650/lib/OpenQA/Utils.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/Utils.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/Utils.pm        2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/Utils.pm        2026-03-02 
15:03:28.000000000 +0100
@@ -306,6 +306,7 @@
     try {
         my ($stdin, $stdout, $stderr) = ('') x 3;
         my @out_args = defined $output_file ? ('>', $output_file, '2>', 
\$stderr) : (\$stdout, \$stderr);
+        local $SIG{CHLD} = 'DEFAULT';
         my $ipc_run_succeeded = IPC::Run::run($cmd, \$stdin, @out_args);
         my $error_code = $?;
         my $return_code;
@@ -338,6 +339,7 @@
         return {
             status => $ipc_run_succeeded,
             return_code => $return_code,
+            exit_status => $ipc_run_succeeded ? 0 : (($return_code // 128 + 
($signal // 127)) & 0xFF || 255),
             stdout => $stdout,
             stderr => $stderr,
             signal => $signal,
@@ -347,6 +349,7 @@
         return {
             status => 0,
             return_code => undef,
+            exit_status => 255,
             stderr => 'an internal error occurred',
             stdout => '',
         };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm 
2026-02-26 09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/WebAPI/Controller/API/V1/Iso.pm 
2026-03-02 15:03:28.000000000 +0100
@@ -265,9 +265,8 @@
     $self->emit_event('openqa_iso_delete', {iso => $iso});
 
     my $schema = $self->schema;
-    my $subquery = $schema->resultset('JobSettings')->query_for_settings({ISO 
=> $iso});
-    my @jobs
-      = $schema->resultset('Jobs')->search({'me.id' => {-in => 
$subquery->get_column('job_id')->as_query}})->all;
+    my $settings_conds = 
$schema->resultset('JobSettings')->conds_for_settings({ISO => $iso});
+    my @jobs = $schema->resultset('Jobs')->search($settings_conds)->all;
 
     for my $job (@jobs) {
         $self->emit_event('openqa_job_delete', {id => $job->id});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 
2026-02-26 09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 
2026-03-02 15:03:28.000000000 +0100
@@ -86,6 +86,7 @@
     $validation->optional('offset')->num;
     $validation->optional('groupid')->num;
     $validation->optional('not_groupid')->num;
+    $validation->optional('job_setting', 'not_empty')->like(qr/.+=.*/);
 
     my $limits = OpenQA::App->singleton->config->{misc_limits};
     my $limit = min($limits->{generic_max_limit}, $validation->param('limit') 
// $limits->{generic_default_limit});
@@ -99,6 +100,7 @@
     my %args;
     $args{limit} = $limit + 1;
     $args{offset} = $offset;
+    $args{job_settings} = $self->every_key_value_param('job_setting');
     my @args = qw(build iso distri version flavor scope group groupid
       not_groupid before after arch hdd_1 test machine worker_class
       modules modules_result);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/lib/OpenQA/WebAPI/Plugin/Helpers.pm 
new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/WebAPI/Plugin/Helpers.pm
--- old/openQA-5.1772092969.74a39650/lib/OpenQA/WebAPI/Plugin/Helpers.pm        
2026-02-26 09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/lib/OpenQA/WebAPI/Plugin/Helpers.pm        
2026-03-02 15:03:28.000000000 +0100
@@ -285,6 +285,7 @@
     $app->helper('reply.validation_error' => \&_validation_error);
     $app->helper(compose_job_overview_search_args => 
\&_compose_job_overview_search_args);
     $app->helper(every_non_empty_param => \&_every_non_empty_param);
+    $app->helper(every_key_value_param => \&_every_key_value_param);
     $app->helper(compute_overview_filtering_params => 
\&_compute_overview_filtering_params);
     $app->helper(groups_for_globs => \&_groups_for_globs);
     $app->helper(param_hash => \&_param_hash);
@@ -350,6 +351,7 @@
     $v->optional('modules', 'comma_separated');
     $v->optional('flavor', 'comma_separated');
     $v->optional('limit', 'not_empty')->num(1, undef);
+    $v->optional('job_setting', 'not_empty')->like(qr/.+=.*/);
     $v->optional('t')->datetime;
 
     # add simple query params to search args
@@ -442,8 +444,9 @@
     # allow filtering by comment text
     if (my $c = $v->param('comment')) { $search_args{comment_text} = $c }
 
-    # allow filtering by several job settings
+    # allow filtering by job settings
     $search_args{filters} = $c->compute_overview_filtering_params;
+    $search_args{job_settings} = $c->every_key_value_param('job_setting');
 
     return (\%search_args, \@groups);
 }
@@ -452,6 +455,10 @@
     [map { split ',', $_ } @{$c->every_param($param_key)}]
 }
 
+sub _every_key_value_param ($c, $param_key) {
+    return {map { split('=', $_, 2) } 
@{$c->validation->every_param($param_key)}};
+}
+
 sub _compute_overview_filtering_params ($c) {
     my $states = $c->every_non_empty_param('state');
     my $results = $c->every_non_empty_param('result');
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1772092969.74a39650/t/10-tests_overview.t 
new/openQA-5.1772460208.7a4e1e06/t/10-tests_overview.t
--- old/openQA-5.1772092969.74a39650/t/10-tests_overview.t      2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/t/10-tests_overview.t      2026-03-02 
15:03:28.000000000 +0100
@@ -501,6 +501,38 @@
     like $summary, qr/Scheduled: 2/i, 'Scheduled jobs remain';
 };
 
+subtest 'Filtering by job settings' => sub {
+    $schema->txn_begin;
+
+    my @basic_settings = (VERSION => 'test_version', DISTRI => 'test_distri');
+    my @search_params = (distri => 'test_distri', version => 'test_version', 
job_setting => ());
+    my @jobs = (create_job(TEST => 'test_job_1', @basic_settings), 
create_job(TEST => 'test_job_2', @basic_settings));
+    $jobs[0]->settings->create({key => 'MY_SETTING', value => 'my_value'});
+    $jobs[0]->settings->create({key => 'ANOTHER_SETTING', value => 
'another_value'});
+    $jobs[1]->settings->create({key => 'MY_SETTING', value => 'other_value'});
+    $jobs[1]->settings->create({key => 'ANOTHER_SETTING', value => 
'another_value'});
+    $jobs[1]->settings->create({key => 'ANOTHER_SETTING', value => 
'yet_another_value'});
+
+    $t->get_ok('/tests/overview', form => {@search_params, 
'MY_SETTING=my_value'})->status_is(200);
+    $t->element_exists('#res-' . $jobs[0]->id, 'job with custom setting 
found');
+    $t->element_exists_not('#res-' . $jobs[1]->id, 'job with different setting 
NOT found');
+
+    $t->get_ok('/tests/overview', form => {@search_params, 
'MY_SETTING=different_value'})->status_is(200);
+    $t->element_exists_not('#res-' . $_->id, 'all jobs filtered out') for 
@jobs;
+
+    $t->get_ok('/tests/overview', form => {@search_params, 
['MY_SETTING=my_value', 'ANOTHER_SETTING=another_value']});
+    $t->status_is(200, 'can filter by more than one job setting');
+    $t->element_exists('#res-' . $jobs[0]->id, 'job with multiple custom 
setting found');
+    $t->element_exists_not('#res-' . $jobs[1]->id, 'job where only one setting 
applies NOT found');
+
+    $t->get_ok('/tests/overview', form => {@search_params, [map { 
"ANOTHER_SETTING=${_}another_value" } '', 'yet_']});
+    $t->status_is(200, 'can filter by the same job setting more than one 
time');
+    $t->element_exists_not('#res-' . $jobs[0]->id, 'job where only one setting 
value applies NOT found');
+    $t->element_exists('#res-' . $jobs[1]->id, 'job with multiple custom 
setting values found');
+
+    $schema->txn_rollback;
+};
+
 subtest 'Meta-filters' => sub {
     
$t->get_ok('/tests/overview?distri=opensuse&version=13.1&build=0091&result=complete')->status_is(200);
     my $summary = get_summary;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1772092969.74a39650/t/25-cache-service.t 
new/openQA-5.1772460208.7a4e1e06/t/25-cache-service.t
--- old/openQA-5.1772092969.74a39650/t/25-cache-service.t       2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/t/25-cache-service.t       2026-03-02 
15:03:28.000000000 +0100
@@ -16,7 +16,10 @@
     $ENV{OPENQA_CACHE_ATTEMPTS} = 3;
     $ENV{OPENQA_CACHE_ATTEMPT_SLEEP_TIME} = 0;
     $ENV{OPENQA_RSYNC_RETRY_PERIOD} = 0;
+    $ENV{OPENQA_RSYNC_RETRIES} = 1;
     $ENV{OPENQA_METRICS_DOWNLOAD_SIZE} = 1024;
+    $ENV{OPENQA_BASE_PORT} = 20000 + int(rand(10000));
+    $ENV{OPENQA_TEST_WAIT_INTERVAL} = 0.05;
 
     $tempdir = tempdir;
     my $basedir = $tempdir->child('t', 'cache.d');
@@ -79,7 +82,7 @@
     
$server_instance->set_pipes(0)->separate_err(0)->blocking_stop(1)->channels(0)->restart;
     
$cache_service->set_pipes(0)->separate_err(0)->blocking_stop(1)->channels(0)->restart;
     perform_minion_jobs($t->app->minion);
-    wait_for_or_bail_out { $cache_client->info->available } 'cache service';
+    wait_for_or_bail_out { $cache_client->info->available } 'cache service', 
{interval => 0.5};
 }
 
 sub test_default_usage ($id, $asset) {
@@ -137,17 +140,19 @@
 }
 
 sub test_download ($id, $asset) {
-    unlink path($cachedir)->child($asset);
+    unlink path($cachedir, "localhost")->child($asset);
     my $asset_request = $cache_client->asset_request(id => $id, asset => 
$asset, type => 'hdd', host => $host);
 
     ok !$cache_client->enqueue($asset_request), "enqueued id $id, asset 
$asset";
 
     my $status = $cache_client->status($asset_request);
     perform_minion_jobs($t->app->minion);
-    $status = $cache_client->status($asset_request) until 
!$status->is_downloading;
+    wait_for_or_bail_out { 
!$cache_client->status($asset_request)->is_downloading } "asset";
+    $status = $cache_client->status($asset_request);
 
     # And then goes to PROCESSED state
-    ok $status->is_processed, 'only other state is processed';
+    ok !$status->has_error, 'no error in status' or die always_explain $status;
+    ok $status->is_processed, 'only other state is processed' or die 
always_explain $status;
     ok exists $status->data->{has_download_error}, 'whether a download error 
happened is added to status info';
     ok !$status->data->{has_download_error}, 'no download error occurred';
 
@@ -327,13 +332,15 @@
 
     my $tot_proc = $ENV{STRESS_TEST} ? 100 : 3;
     my $concurrent = $ENV{STRESS_TEST} ? 30 : 2;
+    my $worker = cache_minion_worker;
+    $worker->start;
+
     my $q = queue;
     $q->pool->maximum_processes($concurrent);
     $q->queue->maximum_processes($tot_proc);
 
     my $concurrent_test = sub {
         if (!$cache_client->enqueue($asset_request)) {
-            perform_minion_jobs($t->app->minion);
             wait_for_or_bail_out { 
$cache_client->status($asset_request)->is_processed } 'asset';
             my $ret = $cache_client->asset_exists('localhost', $asset);
             Devel::Cover::report() if Devel::Cover->can('report');
@@ -344,6 +351,7 @@
     $q->add(process($concurrent_test)->set_pipes(0)->internal_pipes(1)) for 1 
.. $tot_proc;
 
     $q->consume();
+    $worker->stop;
     is $q->done->size, $tot_proc, 'Queue consumed ' . $tot_proc . ' processes';
     $q->done->each(
         sub {
@@ -359,7 +367,7 @@
     my $asset = '[email protected]';
     my $asset_request = $cache_client->asset_request(id => 922756, asset => 
$asset, type => 'hdd', host => $host);
 
-    unlink path($cachedir)->child($asset);
+    unlink path($cachedir, 'localhost')->child($asset);
     ok !$cache_client->asset_exists('localhost', $asset), 'Asset absent'
       or die diag 'Asset already exists - abort test';
 
@@ -382,7 +390,7 @@
     my $asset = '[email protected]';
     my $asset_request = $cache_client->asset_request(id => 922756, asset => 
$asset, type => 'hdd', host => $host);
 
-    unlink path($cachedir)->child($asset);
+    unlink path($cachedir, 'localhost')->child($asset);
     ok !$cache_client->asset_exists('localhost', $asset), 'Asset absent'
       or die diag 'Asset already exists - abort test';
 
@@ -409,9 +417,14 @@
     my $worker_4 = cache_minion_worker;
 
     $_->start for ($worker_2, $worker_3, $worker_4);
+    wait_for_or_bail_out {
+        my $stats = $cache_client->info->data;
+        ($stats->{active_workers} // 0) + ($stats->{inactive_workers} // 0) >= 
3
+    }
+    '3 minion workers';
 
     my @assets = map { "sle-12-SP3-x86_64-0368-200_$_\@64bit.qcow2" } 1 .. 
$tot_proc;
-    unlink path($cachedir)->child($_) for @assets;
+    unlink path($cachedir, "localhost")->child($_) for @assets;
     my %requests
       = map { $_ => $cache_client->asset_request(id => 922756, asset => $_, 
type => 'hdd', host => $host) } @assets;
     ok !$cache_client->enqueue($requests{$_}), "Download enqueued for $_" for 
@assets;
@@ -422,12 +435,13 @@
     'assets';
 
     for my $asset (@assets) {
-        ok wait_for(sub { $cache_client->asset_exists('localhost', $asset) }, 
"Asset $asset downloaded correctly"),
-          "Asset $asset downloaded correctly";
+        my $status = $cache_client->status($requests{$asset});
+        ok $status->is_success, "Asset $asset downloaded successfully" or die 
always_explain $status;
+        ok $cache_client->asset_exists('localhost', $asset), "Asset $asset 
exists on disk";
     }
 
     @assets = map { '[email protected]' } 1 .. 
$tot_proc;
-    unlink path($cachedir)->child($_) for @assets;
+    unlink path($cachedir, "localhost")->child($_) for @assets;
     %requests
       = map { $_ => $cache_client->asset_request(id => 922756, asset => $_, 
type => 'hdd', host => $host) } @assets;
     ok !$cache_client->enqueue($requests{$_}), "Download enqueued for $_" for 
@assets;
@@ -438,9 +452,10 @@
     'assets';
 
     for my $asset (@assets) {
-        ok wait_for(sub { $cache_client->asset_exists('localhost', 
'[email protected]') },
-            "Asset $asset downloaded correctly"),
-          "Asset $asset downloaded correctly";
+        my $status = $cache_client->status($requests{$asset});
+        ok $status->is_success, "Asset $asset downloaded successfully" or die 
always_explain $status;
+        ok $cache_client->asset_exists('localhost', 
'[email protected]'),
+          "Asset $asset exists on disk";
     }
 
     $_->stop for ($worker_2, $worker_3, $worker_4);
@@ -504,55 +519,40 @@
         is $check_count->(), 0, 'count back at 0';
     };
 
-    my $url = $cache_client->url('/influxdb/minion');
+    my $cs_port = service_port "cache_service";
+    my $url = $cache_client->url("/influxdb/minion");
     my $ua = $cache_client->ua;
-    my $res = $ua->get($url)->result;
-    is $res->body, <<"EOF", 'three workers still running';
-openqa_minion_jobs,url=http://127.0.0.1:9530 
active=0i,delayed=0i,failed=0i,inactive=0i
-openqa_minion_workers,url=http://127.0.0.1:9530 
active=0i,inactive=2i,registered=2i
-openqa_download_count,url=http://127.0.0.1:9530 count=${count}i
-openqa_download_rate,url=http://127.0.0.1:9530 bytes=${rate}i
+
+    my $check_influx
+      = sub ($active_jobs, $inactive_jobs, $failed_jobs, $delayed_jobs, 
$active_workers, $inactive_workers,
+        $registered_workers, $msg)
+    {
+        my $res = $ua->get($url)->result;
+        is $res->body, <<"EOF", $msg;
+openqa_minion_jobs,url=http://127.0.0.1:${cs_port} 
active=${active_jobs}i,delayed=${delayed_jobs}i,failed=${failed_jobs}i,inactive=${inactive_jobs}i
+openqa_minion_workers,url=http://127.0.0.1:${cs_port} 
active=${active_workers}i,inactive=${inactive_workers}i,registered=${registered_workers}i
+openqa_download_count,url=http://127.0.0.1:${cs_port} count=${count}i
+openqa_download_rate,url=http://127.0.0.1:${cs_port} bytes=${rate}i
 EOF
+    };
+
+    $check_influx->(0, 0, 0, 0, 0, 2, 2, 'three workers still running');
 
     my $minion = $app->minion;
     my $worker = $minion->repair->worker->register;
-    $res = $ua->get($url)->result;
-    is $res->body, <<"EOF", 'four workers running now';
-openqa_minion_jobs,url=http://127.0.0.1:9530 
active=0i,delayed=0i,failed=0i,inactive=0i
-openqa_minion_workers,url=http://127.0.0.1:9530 
active=0i,inactive=3i,registered=3i
-openqa_download_count,url=http://127.0.0.1:9530 count=${count}i
-openqa_download_rate,url=http://127.0.0.1:9530 bytes=${rate}i
-EOF
+    $check_influx->(0, 0, 0, 0, 0, 3, 3, 'four workers running now');
 
     $minion->add_task(test => sub { });
     my $job_id = $minion->enqueue('test');
     my $job_id2 = $minion->enqueue('test');
     my $job = $worker->dequeue(0);
-    $res = $ua->get($url)->result;
-    is $res->body, <<"EOF", 'two jobs';
-openqa_minion_jobs,url=http://127.0.0.1:9530 
active=1i,delayed=0i,failed=0i,inactive=1i
-openqa_minion_workers,url=http://127.0.0.1:9530 
active=1i,inactive=2i,registered=3i
-openqa_download_count,url=http://127.0.0.1:9530 count=${count}i
-openqa_download_rate,url=http://127.0.0.1:9530 bytes=${rate}i
-EOF
+    $check_influx->(1, 1, 0, 0, 1, 2, 3, 'two jobs');
 
     $job->fail('test');
-    $res = $ua->get($url)->result;
-    is $res->body, <<"EOF", 'one job failed';
-openqa_minion_jobs,url=http://127.0.0.1:9530 
active=0i,delayed=0i,failed=1i,inactive=1i
-openqa_minion_workers,url=http://127.0.0.1:9530 
active=0i,inactive=3i,registered=3i
-openqa_download_count,url=http://127.0.0.1:9530 count=${count}i
-openqa_download_rate,url=http://127.0.0.1:9530 bytes=${rate}i
-EOF
+    $check_influx->(0, 1, 1, 0, 0, 3, 3, 'one job failed');
 
     $job->retry({delay => ONE_HOUR});
-    $res = $ua->get($url)->result;
-    is $res->body, <<"EOF", 'job is being retried';
-openqa_minion_jobs,url=http://127.0.0.1:9530 
active=0i,delayed=1i,failed=0i,inactive=2i
-openqa_minion_workers,url=http://127.0.0.1:9530 
active=0i,inactive=3i,registered=3i
-openqa_download_count,url=http://127.0.0.1:9530 count=${count}i
-openqa_download_rate,url=http://127.0.0.1:9530 bytes=${rate}i
-EOF
+    $check_influx->(0, 2, 0, 1, 0, 3, 3, 'job is being retried');
 };
 
 subtest 'Concurrent downloads of the same file' => sub {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1772092969.74a39650/t/api/02-iso.t 
new/openQA-5.1772460208.7a4e1e06/t/api/02-iso.t
--- old/openQA-5.1772092969.74a39650/t/api/02-iso.t     2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/t/api/02-iso.t     2026-03-02 
15:03:28.000000000 +0100
@@ -360,7 +360,7 @@
 is $t->tx->res->json->{job}->{state}, 'scheduled', "new job $newid is 
scheduled";
 
 # cancel the iso
-$t->post_ok("/api/v1/isos/$iso/cancel")->status_is(200);
+$t->post_ok("/api/v1/isos/$iso/cancel")->status_is(200)->json_is('/result', 
16, 'iso was cancelled');
 
 $t->get_ok("/api/v1/jobs/$newid")->status_is(200);
 is $t->tx->res->json->{job}->{state}, 'cancelled', "job $newid is cancelled";
@@ -378,7 +378,7 @@
 $t->delete_ok("/api/v1/isos/$iso")->status_is(403);
 # switch to admin and continue
 client($t, apikey => 'ARTHURKEY01', apisecret => 'EXCALIBUR');
-$t->delete_ok("/api/v1/isos/$iso")->status_is(200);
+$t->delete_ok("/api/v1/isos/$iso")->status_is(200)->json_is('/count', 23, 
'jobs deleted');
 # now the jobs should be gone
 $t->get_ok("/api/v1/jobs/$newid")->status_is(404);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1772092969.74a39650/t/api/04-jobs.t 
new/openQA-5.1772460208.7a4e1e06/t/api/04-jobs.t
--- old/openQA-5.1772092969.74a39650/t/api/04-jobs.t    2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/t/api/04-jobs.t    2026-03-02 
15:03:28.000000000 +0100
@@ -86,6 +86,10 @@
 my @jobs = @{$t->tx->res->json->{jobs}};
 my $jobs_count = scalar @jobs;
 
+sub job_ids {
+    [sort map { $_->{id} } @{$t->tx->res->json->{jobs}}];
+}
+
 subtest 'initial state of jobs listing' => sub {
     is $jobs_count, 18;
     my %jobs = map { $_->{id} => $_ } @jobs;
@@ -145,13 +149,17 @@
     is scalar @jobs, 4, 'jobs of specified groups are excluded';
 };
 
-subtest 'restricted query' => sub {
+subtest 'filtering jobs' => sub {
     $t->get_ok('/api/v1/jobs?iso=openSUSE-13.1-DVD-i586-Build0091-Media.iso');
-    is scalar(@{$t->tx->res->json->{jobs}}), 6, 'query for existing jobs by 
iso';
+    is_deeply job_ids(), [99927, 99928, 99937, 99944, 99945, 99946], 
'filtering by iso';
     $t->get_ok('/api/v1/jobs?build=0091');
-    is scalar(@{$t->tx->res->json->{jobs}}), 11, 'query for existing jobs by 
build';
+    is_deeply job_ids(), [99764, 99927, 99928, 99937, 99944 .. 99946, 99961 .. 
99963, 99981], 'filtering by build';
     $t->get_ok('/api/v1/jobs?hdd_1=openSUSE-13.1-x86_64.hda');
-    is scalar(@{$t->tx->res->json->{jobs}}), 3, 'query for existing jobs by 
hdd_1';
+    is_deeply job_ids(), [99936, 99939, 99946], 'filtering by HDD_1';
+    
$t->get_ok('/api/v1/jobs?iso=openSUSE-Factory-DVD-x86_64-Build0048-Media.iso&hdd_1=openSUSE-13.1-x86_64.hda');
+    is_deeply job_ids(), [99936, 99939], 'filters can be combined';
+    
$t->get_ok('/api/v1/jobs?distri=opensuse&job_setting=DESKTOP=kde&job_setting=QEMUCPU=qemu64');
+    is_deeply job_ids(), [99936, 99938, 99939, 99940], 'filtering by arbitrary 
job settings';
 };
 
 subtest 'argument combinations' => sub {
@@ -1397,7 +1405,7 @@
     $query->query(worker_class => ':UFP:');
     $t->get_ok($query->path_query)->status_is(200);
     $res = $t->tx->res->json;
-    ok @{$res->{jobs}} eq 1, 'Known worker class group exists, and returns one 
job';
+    is @{$res->{jobs}}, 1, 'Known worker class group exists, and returns one 
job';
 
     $t->json_is('/jobs/0/settings/WORKER_CLASS' => ':UFP:NCC1701F', 'Correct 
worker class');
 };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/t/lib/OpenQA/Test/Utils.pm 
new/openQA-5.1772460208.7a4e1e06/t/lib/OpenQA/Test/Utils.pm
--- old/openQA-5.1772092969.74a39650/t/lib/OpenQA/Test/Utils.pm 2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/t/lib/OpenQA/Test/Utils.pm 2026-03-02 
15:03:28.000000000 +0100
@@ -214,8 +214,10 @@
     # produces a test failure in case any relevant sub process terminated with 
a non-zero exit code
     # note: This function is supposed to be called from the SIGCHLD handler. 
It seems to have no effect to
     #       call die or BAIL_OUT from that handler so fail and _exit is used 
instead.
-    while ((my $pid = waitpid(-1, WNOHANG)) > 0) {
-        next unless my $child_name = delete $RELEVANT_CHILD_PIDS{$pid};
+    for my $pid (keys %RELEVANT_CHILD_PIDS) {
+        my $wait_pid = waitpid($pid, WNOHANG);
+        next unless $wait_pid > 0;
+        my $child_name = delete $RELEVANT_CHILD_PIDS{$pid};
         my $exit_status = $?;
         my $exit_signal = $exit_status & 127;
         _fail_and_exit "sub process $child_name terminated by signal 
$exit_signal", $exit_signal if $exit_signal;
@@ -571,12 +573,13 @@
 sub wait_for : prototype(&*;*) {    # `&*;*` allows calling it like `wait_for 
{ 1 } 'foo'`
     my ($function, $description, $args) = @_;
     my $timeout = $args->{timeout} // 60;
-    my $interval = $args->{interval} // .1;
+    my $interval = $args->{interval} // $ENV{OPENQA_TEST_WAIT_INTERVAL} // .1;
 
     note "Waiting for '$description' to become available (timeout: $timeout)";
-    while ($timeout > 0) {
+    my $end = Time::HiRes::time() + $timeout;
+    while (Time::HiRes::time() < $end) {
         return 1 if $function->();
-        $timeout -= sleep $interval;    # uncoverable statement (function 
might return early one line up)
+        sleep $interval;
     }
     return 0;    # uncoverable statement (only invoked if tests would fail)
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1772092969.74a39650/tools/unstable_tests.txt 
new/openQA-5.1772460208.7a4e1e06/tools/unstable_tests.txt
--- old/openQA-5.1772092969.74a39650/tools/unstable_tests.txt   2026-02-26 
09:02:49.000000000 +0100
+++ new/openQA-5.1772460208.7a4e1e06/tools/unstable_tests.txt   2026-03-02 
15:03:28.000000000 +0100
@@ -1,3 +1,2 @@
-t/25-cache-service.t
 t/ui/26-jobs_restart.t
 t/ui/13-admin.t

++++++ openQA.obsinfo ++++++
--- /var/tmp/diff_new_pack.rt0fYV/_old  2026-03-02 17:42:39.005124897 +0100
+++ /var/tmp/diff_new_pack.rt0fYV/_new  2026-03-02 17:42:39.017125392 +0100
@@ -1,5 +1,5 @@
 name: openQA
-version: 5.1772092969.74a39650
-mtime: 1772092969
-commit: 74a396508c9a0468c077a85c120fbdbac5bcb04e
+version: 5.1772460208.7a4e1e06
+mtime: 1772460208
+commit: 7a4e1e0611b430dcb2494faa1b0b43bed5899a5c
 

Reply via email to