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-11-04 19:44:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/openQA (Old)
 and      /work/SRC/openSUSE:Factory/.openQA.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "openQA"

Wed Nov  4 19:44:23 2026 rev:903 rq:1394758 version:5.1793722468.aa0c30fc

Changes:
--------
--- /work/SRC/openSUSE:Factory/openQA/openQA.changes    2026-11-03 
17:56:56.321765628 +0100
+++ /work/SRC/openSUSE:Factory/.openQA.new.3759290/openQA.changes       
2026-11-04 19:44:30.965040926 +0100
@@ -1,0 +2,38 @@
+Tue Nov 03 16:14:56 UTC 2026 - [email protected]
+
+- Update to version 5.1793722468.aa0c30fc:
+  * docs: Mention the `max_connections` PostgreSQL setting
+
+-------------------------------------------------------------------
+Tue Nov 03 05:54:25 UTC 2026 - [email protected]
+
+- Update to version 5.1793682485.ae04aa33:
+  * chore(deps): Dependency cron 2026-11-03
+
+-------------------------------------------------------------------
+Tue Nov 03 02:54:27 UTC 2026 - [email protected]
+
+- Update to version 5.1793674456.f48b3512:
+  * fix(auth): log stray OpenID callbacks as info
+
+-------------------------------------------------------------------
+Mon Nov 02 18:54:24 UTC 2026 - [email protected]
+
+- Update to version 5.1793643862.95842f6a:
+  * feat(worker): Add upload inactivity timeout
+  * feat(worker): Add upload timeout via MAX_UPLOAD_TIME
+
+-------------------------------------------------------------------
+Mon Nov 02 18:02:36 UTC 2026 - [email protected]
+
+- Update to version 5.1793641808.5ee26040:
+  * test(scalability): Avoid running dummy isotovideo in parallel
+  * test(scalability): Fix sporadic test failure
+
+-------------------------------------------------------------------
+Mon Nov 02 16:54:26 UTC 2026 - [email protected]
+
+- Update to version 5.1793635054.fe4b39a3:
+  * feat(docs): add Ecosystem guide
+
+-------------------------------------------------------------------

Old:
----
  openQA-5.1793632686.cbcf1318.obscpio

New:
----
  openQA-5.1793722468.aa0c30fc.obscpio

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

Other differences:
------------------
++++++ openQA-client-test.spec ++++++
--- /var/tmp/diff_new_pack.hTaxCs/_old  2026-11-04 19:44:33.781158805 +0100
+++ /var/tmp/diff_new_pack.hTaxCs/_new  2026-11-04 19:44:33.783158888 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-client
 Name:           %{short_name}-test
-Version:        5.1793632686.cbcf1318
+Version:        5.1793722468.aa0c30fc
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA-test.spec ++++++
--- /var/tmp/diff_new_pack.hTaxCs/_old  2026-11-04 19:44:33.841161316 +0100
+++ /var/tmp/diff_new_pack.hTaxCs/_new  2026-11-04 19:44:33.843161400 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA
 Name:           %{short_name}-test
-Version:        5.1793632686.cbcf1318
+Version:        5.1793722468.aa0c30fc
 Release:        0
 Summary:        Test package for openQA
 License:        GPL-2.0-or-later

++++++ openQA-worker-test.spec ++++++
--- /var/tmp/diff_new_pack.hTaxCs/_old  2026-11-04 19:44:33.891163409 +0100
+++ /var/tmp/diff_new_pack.hTaxCs/_new  2026-11-04 19:44:33.892163451 +0100
@@ -18,7 +18,7 @@
 
 %define         short_name openQA-worker
 Name:           %{short_name}-test
-Version:        5.1793632686.cbcf1318
+Version:        5.1793722468.aa0c30fc
 Release:        0
 Summary:        Test package for %{short_name}
 License:        GPL-2.0-or-later

++++++ openQA.spec ++++++
--- /var/tmp/diff_new_pack.hTaxCs/_old  2026-11-04 19:44:33.960166298 +0100
+++ /var/tmp/diff_new_pack.hTaxCs/_new  2026-11-04 19:44:33.962166381 +0100
@@ -104,7 +104,7 @@
 %define devel_requires %devel_no_selenium_requires chromedriver
 
 Name:           openQA
-Version:        5.1793632686.cbcf1318
+Version:        5.1793722468.aa0c30fc
 Release:        0
 Summary:        Framework for automated system-level testing (web-frontend, 
scheduler and tools)
 Group:          Development/Tools/Other

++++++ openQA-5.1793632686.cbcf1318.obscpio -> 
openQA-5.1793722468.aa0c30fc.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/docs/Contributing.md 
new/openQA-5.1793722468.aa0c30fc/docs/Contributing.md
--- old/openQA-5.1793632686.cbcf1318/docs/Contributing.md       2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/docs/Contributing.md       2026-11-03 
17:14:28.000000000 +0100
@@ -109,6 +109,10 @@
 about the complete project but without a guarantee for correctness:
 <https://deepwiki.com/os-autoinst/openQA>
 
+The surrounding tools, public instances, learning resources, and community
+channels are listed in the [Ecosystem guide](Ecosystem.md#ecosystem), which is
+also the place to add your own instance, tool, blog, or video.
+
 ## Technologies
 
 Most in openQA, from `os-autoinst` to the web frontend and from the tests to
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/docs/Ecosystem.md 
new/openQA-5.1793722468.aa0c30fc/docs/Ecosystem.md
--- old/openQA-5.1793632686.cbcf1318/docs/Ecosystem.md  1970-01-01 
01:00:00.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/docs/Ecosystem.md  2026-11-03 
17:14:28.000000000 +0100
@@ -0,0 +1,100 @@
+<a id="ecosystem"></a>
+
+# Ecosystem
+
+## Introduction
+
+openQA is more than a single tool. The core is two repositories,
+[openQA](https://github.com/os-autoinst/openQA) (web UI, scheduler, worker)
+and [os-autoinst](https://github.com/os-autoinst/os-autoinst) (the backend
+that runs the system under test). The ecosystem is everything built around
+them: the many other projects in the
+[os-autoinst organization](https://github.com/os-autoinst), public instances,
+learning resources, and a community. This page is a discovery guide for users
+and a place for contributors to add their instance, project, blog, or video
+with a pull request.
+
+## Related projects
+
+Beyond the core repos, the
+[os-autoinst organization](https://github.com/os-autoinst) hosts many more
+projects, e.g. the test distributions and needles:
+
+- 
[os-autoinst-distri-opensuse](https://github.com/os-autoinst/os-autoinst-distri-opensuse):
+  the openSUSE and SLE tests, conducted on
+  [openqa.opensuse.org](https://openqa.opensuse.org).
+- 
[os-autoinst-distri-example](https://github.com/os-autoinst/os-autoinst-distri-example):
+  a template to start writing tests for a new operating system.
+- 
[os-autoinst-needles-opensuse](https://github.com/os-autoinst/os-autoinst-needles-opensuse):
+  reference images not already included in the test distribution.
+
+The list is intentionally short: the organization contains many more projects,
+so browse it directly rather than enumerating them here, which would quickly
+go stale.
+
+Third-party tools live outside the organization, e.g.
+[Haystack](https://gitlab.gnome.org/jwharm/Haystack)
+([Flathub](https://flathub.org/en/apps/app.drey.Haystack)), a GNOME app to
+edit openQA needles.
+
+## Public instances
+
+Anyone can run an openQA instance, so this is a non-exhaustive list of known
+public instances, not an authoritative registry. openQA is one part of each
+project's test infrastructure, not necessarily its primary quality gateway.
+Operators who prefer not to be listed can request removal.
+
+- [openqa.opensuse.org](https://openqa.opensuse.org): openSUSE.
+- [openqa.suse.de](https://openqa.suse.de): SUSE SLE (private).
+- [Fedora](https://openqa.fedoraproject.org/): Fedora.
+- [AlmaLinux](http://openqa.almalinux.org/): AlmaLinux.
+- [Debian](https://openqa.debian.net/): Debian.
+- [Qubes OS](https://openqa.qubes-os.org/): Qubes OS.
+- [Endless OS](https://openqa.endlessm.com/): Endless OS.
+- [GNOME](https://openqa.gnome.org): the GNOME project.
+- KDE: see the
+  [KDE blog post on openQA testing in KDE 
Linux](https://blogs.kde.org/2026-07-13/openqa-testing-in-kde-linux/).
+- openSUSE KDE contributors:
+  [group 23](https://openqa.opensuse.org/group_overview/23).
+- openSUSE GNOME contributors:
+  [group 35](https://openqa.opensuse.org/group_overview/35).
+- OBS developers:
+  [parent group 
7](https://openqa.opensuse.org/parent_group_overview/7#grouped_by_build).
+- wicked developers:
+  [wicked-ci](https://gitlab.suse.de/wicked-maintainers/wicked-ci#openqa).
+- openQA-in-openQA (the team's own tests):
+  [group 24](https://openqa.opensuse.org/group_overview/24).
+
+See also the
+[openSUSE:OpenQA/Partners](https://en.opensuse.org/openSUSE:OpenQA/Partners)
+page.
+
+## Learning resources
+
+- [openqa-bites](https://openqa-bites.github.io/): stories and tutorials from
+  an openQA developer.
+- [Liv's blog](https://liv.pink/): guides and hints from an openQA developer.
+- [EuroLinux on 
openQA](https://en.euro-linux.com/blog/openqa-or-how-we-test-eurolinux/):
+  how EuroLinux tests with openQA.
+- [Codethink on automated Linux kernel 
testing](https://www.codethink.co.uk/articles/2021/automated-linux-kernel-testing/).
+- [SUSE QE Tools 
Workshop](https://youtube.com/playlist?list=PL6TdzZgl22k7GD62L292tn3dBTg1JPmVF):
+  a playlist covering many topics.
+- [Conference presentations on 
media.ccc.de](https://media.ccc.de/search/?q=openQA).
+- [Various videos on 
YouTube](https://www.youtube.com/results?search_query=openQA).
+- [Linux Inlaws podcast featuring 
openQA](https://archive.org/download/LI_S02E16_OpenQA__0D80/LI_S02E16_OpenQA_.mp3).
+- [Testing in a Box: Streamlining Embedded Systems 
Testing](https://www.youtube.com/watch?v=Q2Y1YQ8V5gk).
+
+## Community and contributing
+
+- The [openQAv3 project](https://progress.opensuse.org/projects/openqav3) in
+  openSUSE's project management tool coordinates the development roadmap.
+- The QA testing monthly call:
+  [GNOME QA testing monthly 
call](https://gitlab.gnome.org/GNOME/openqa-tests/-/wikis/QA-testing-monthly-call).
+- The official openSUSE presence on
+  [Reddit](https://www.reddit.com/r/opensuse/) and openQA on
+  [X/Twitter by openQAhq](https://twitter.com/openQAhq).
+
+If you are contributing to openQA by running an instance, writing a tool, a
+post, a video, or a podcast, add a link to the relevant section above with a
+pull request to
+[`docs/Ecosystem.md`](https://github.com/os-autoinst/openQA/blob/master/docs/Ecosystem.md).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/docs/GettingStarted.md 
new/openQA-5.1793722468.aa0c30fc/docs/GettingStarted.md
--- old/openQA-5.1793632686.cbcf1318/docs/GettingStarted.md     2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/docs/GettingStarted.md     2026-11-03 
17:14:28.000000000 +0100
@@ -40,6 +40,8 @@
 existing instance see the [Users Guide](UsersGuide.md#usersguide).
 More advanced topics can be found in other documents. All documents are also
 available in the [official repository](https://github.com/os-autoinst/openQA).
+For the surrounding tools, public instances, learning resources, and community
+see the [Ecosystem guide](Ecosystem.md#ecosystem).
 
 ## Architecture
 
@@ -188,8 +190,9 @@
   via `record_soft_failure` (from os-autoinst) or a job failure has been 
ignored
   explicitly via a [job label](UsersGuide.md#labels).
 
-- **timeout_exceeded** The job was aborted because `MAX_JOB_TIME` or 
`MAX_SETUP_TIME`
-  has been exceeded, see [Changing timeout](WritingTests.md#changing_timeouts) 
for details.
+- **timeout_exceeded** The job was aborted because `MAX_JOB_TIME`, 
`MAX_SETUP_TIME`,
+  `MAX_UPLOAD_TIME`, or `UPLOAD_INACTIVITY_TIMEOUT` has been exceeded, see
+  [Changing timeout](WritingTests.md#changing_timeouts) for details.
 
 - **skipped** Dependencies failed so the job was not started.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/docs/Installing.md 
new/openQA-5.1793722468.aa0c30fc/docs/Installing.md
--- old/openQA-5.1793632686.cbcf1318/docs/Installing.md 2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/docs/Installing.md 2026-11-03 
17:14:28.000000000 +0100
@@ -556,6 +556,11 @@
 value format can be found in the
 [DBD::Pg documentation](https://metacpan.org/pod/DBD::Pg#DBI-Class-Methods).
 
+No specific PostgreSQL configuration is required. The default connection limit
+of 100 is sufficient for most openQA instances. If you connect additional
+tooling like Telegraf, you might need to increase `max_connections` in
+`/var/lib/pgsql/data/postgresql.conf`, though.
+
 #### Example for connecting to local PostgreSQL database
 
 ```ini
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/docs/WritingTests.md 
new/openQA-5.1793722468.aa0c30fc/docs/WritingTests.md
--- old/openQA-5.1793632686.cbcf1318/docs/WritingTests.md       2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/docs/WritingTests.md       2026-11-03 
17:14:28.000000000 +0100
@@ -498,10 +498,15 @@
 
 The download of assets, synchronization of tests and other setup tasks do
 **not** count into `MAX_JOB_TIME`. However, the setup time is limited by 
default
-to one hour. This can be changed by setting `MAX_SETUP_TIME`. The variable
-`TIMEOUT_SCALE` allows to scale `MAX_JOB_TIME` and timeouts within the backend,
-for example the [test API](#api). This is supposed to be set within the worker
-settings on slow worker hosts. It has no influence on the video setting.
+to one hour. This can be changed by setting `MAX_SETUP_TIME`. Similarly, the
+teardown and upload phase is limited by default to 4 hours, which can be
+changed by setting `MAX_UPLOAD_TIME`. Additionally, an inactivity timeout of
+by default 20 minutes applies during upload, which can be changed via
+`UPLOAD_INACTIVITY_TIMEOUT`. The variable `TIMEOUT_SCALE` allows to scale
+`MAX_JOB_TIME`, `MAX_UPLOAD_TIME`, `UPLOAD_INACTIVITY_TIMEOUT`, and timeouts
+within the backend, for example the [test API](#api). This is supposed to be
+set within the worker settings on slow worker hosts. It has no influence on the
+video setting.
 
 ### Enabling UEFI with QEMU
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/docs/index.md 
new/openQA-5.1793722468.aa0c30fc/docs/index.md
--- old/openQA-5.1793632686.cbcf1318/docs/index.md      2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/docs/index.md      2026-11-03 
17:14:28.000000000 +0100
@@ -7,6 +7,7 @@
 include::Pitfalls.md[]
 include::Networking.md[]
 include::Contributing.md[]
+include::Ecosystem.md[]
 include::Branding.md[]
 include::ContainerizedSetup.md[]
 include::PrivacyPolicy.md[]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/lib/OpenQA/Constants.pm 
new/openQA-5.1793722468.aa0c30fc/lib/OpenQA/Constants.pm
--- old/openQA-5.1793632686.cbcf1318/lib/OpenQA/Constants.pm    2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/lib/OpenQA/Constants.pm    2026-11-03 
17:14:28.000000000 +0100
@@ -93,6 +93,12 @@
 # The max. time the job setup (asset caching, test syncing) is allowed to take 
before the worker stops it.
 use constant DEFAULT_MAX_SETUP_TIME => ONE_HOUR;
 
+# The max. time uploading assets and logs is allowed to take before the worker 
stops it.
+use constant DEFAULT_MAX_UPLOAD_TIME => 4 * ONE_HOUR;
+
+# The max. time an upload is allowed to be inactive before the worker aborts 
it.
+use constant DEFAULT_UPLOAD_INACTIVITY_TIMEOUT => 20 * ONE_MINUTE;
+
 # The smallest time difference of database timestamps we usually distinguish 
in seconds
 # note: PostgreSQL actually provides a higher accuracy for the timestamp type. 
However,
 #       the automatic timestamp handling provided by DBIx only stores whole 
seconds. The
@@ -135,7 +141,7 @@
   MAX_TIMER MIN_TIMER
   DEFAULT_MAX_JOB_TIME
   DEFAULT_MAX_SETUP_TIME
-  DEFAULT_UPLOAD_CHUNK_SIZE API_KEY_COMMENT_MAX_LENGTH
+  DEFAULT_MAX_UPLOAD_TIME DEFAULT_UPLOAD_INACTIVITY_TIMEOUT 
DEFAULT_UPLOAD_CHUNK_SIZE API_KEY_COMMENT_MAX_LENGTH
   DB_TIMESTAMP_ACCURACY
   VIDEO_FILE_NAME_START VIDEO_FILE_NAME_REGEX
   FRAGMENT_REGEX
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1793632686.cbcf1318/lib/OpenQA/WebAPI/Auth/OpenID.pm 
new/openQA-5.1793722468.aa0c30fc/lib/OpenQA/WebAPI/Auth/OpenID.pm
--- old/openQA-5.1793632686.cbcf1318/lib/OpenQA/WebAPI/Auth/OpenID.pm   
2026-11-02 16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/lib/OpenQA/WebAPI/Auth/OpenID.pm   
2026-11-03 17:14:28.000000000 +0100
@@ -111,11 +111,17 @@
     $csr->handle_server_response(
         not_openid => sub () {
             my $op_uri = $params{'openid.op_endpoint'};
-            my $msg
-              = $op_uri
-              ? "OpenID provider '$op_uri' returned invalid data on a login 
attempt"
-              : 'OpenID response returned no provider endpoint';
-            $err_handler->('Failed to login', $msg);
+            if ($op_uri) {
+                $err_handler->('Failed to login', "OpenID provider '$op_uri' 
returned invalid data on a login attempt");
+            }
+            elsif (grep { /^openid\./ } keys %params) {
+                $err_handler->('Failed to login', 'OpenID response returned no 
provider endpoint');
+            }
+            else {
+                # A callback hit without any openid.* parameters is a stray or 
scanner request, not a failed login
+                $c->app->log->info('OpenID: Response callback invoked without 
OpenID parameters');
+                return (error => 0);
+            }
         },
         setup_needed => sub ($setup_url) {
             # Redirect the user to $setup_url
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1793632686.cbcf1318/lib/OpenQA/Worker/Job.pm 
new/openQA-5.1793722468.aa0c30fc/lib/OpenQA/Worker/Job.pm
--- old/openQA-5.1793632686.cbcf1318/lib/OpenQA/Worker/Job.pm   2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/lib/OpenQA/Worker/Job.pm   2026-11-03 
17:14:28.000000000 +0100
@@ -5,8 +5,8 @@
 use Mojo::Base 'Mojo::EventEmitter', -signatures;
 
 use OpenQA::Constants qw(DEFAULT_MAX_JOB_TIME DEFAULT_UPLOAD_CHUNK_SIZE 
DEFAULT_MAX_SETUP_TIME WORKER_COMMAND_ABORT
-  WORKER_COMMAND_QUIT WORKER_COMMAND_CANCEL WORKER_COMMAND_OBSOLETE 
WORKER_SR_BROKEN WORKER_SR_SETUP_FAILURE
-  WORKER_SR_API_FAILURE WORKER_SR_TIMEOUT WORKER_SR_DONE WORKER_SR_DIED 
DOWNLOAD_PERMISSION);
+  DEFAULT_MAX_UPLOAD_TIME DEFAULT_UPLOAD_INACTIVITY_TIMEOUT 
WORKER_COMMAND_QUIT WORKER_COMMAND_CANCEL WORKER_COMMAND_OBSOLETE
+  WORKER_SR_BROKEN WORKER_SR_SETUP_FAILURE WORKER_SR_API_FAILURE 
WORKER_SR_TIMEOUT WORKER_SR_DONE WORKER_SR_DIED DOWNLOAD_PERMISSION);
 use OpenQA::Jobs::Constants;
 use OpenQA::Worker::Engines::isotovideo;
 use OpenQA::Worker::Isotovideo::Client;
@@ -98,7 +98,8 @@
 }
 
 sub DESTROY ($self) {
-    $self->_remove_timer unless ${^GLOBAL_PHASE} eq 'DESTRUCT';
+    $self->_remove_timer([qw(_upload_results_timer _timeout_timer 
_upload_timeout_timer _upload_inactivity_timer)])
+      unless ${^GLOBAL_PHASE} eq 'DESTRUCT';
 }
 
 sub _remove_timer ($self, $timer_names = undef) {
@@ -120,6 +121,7 @@
     $event_data->{job} = $self;
     $event_data->{status} = $status;
     $self->{_status} = $status;
+    $self->_remove_timer([qw(_upload_timeout_timer _upload_inactivity_timer)]) 
if $status eq 'stopped';
     $self->emit(status_changed => $event_data);
 }
 
@@ -194,11 +196,19 @@
 sub _compute_timeouts ($job_settings) {
     my $max_job_time = $job_settings->{MAX_JOB_TIME};
     my $max_setup_time = $job_settings->{MAX_SETUP_TIME};
+    my $max_upload_time = $job_settings->{MAX_UPLOAD_TIME};
+    my $upload_inactivity_timeout = $job_settings->{UPLOAD_INACTIVITY_TIMEOUT};
     my $timeout_scale = $job_settings->{TIMEOUT_SCALE};
     $max_job_time = DEFAULT_MAX_JOB_TIME unless looks_like_number 
$max_job_time;
     $max_setup_time = DEFAULT_MAX_SETUP_TIME unless looks_like_number 
$max_setup_time;
-    $max_job_time *= $timeout_scale if looks_like_number $timeout_scale;
-    return ($max_job_time, $max_setup_time);
+    $max_upload_time = DEFAULT_MAX_UPLOAD_TIME unless looks_like_number 
$max_upload_time;
+    $upload_inactivity_timeout = DEFAULT_UPLOAD_INACTIVITY_TIMEOUT unless 
looks_like_number $upload_inactivity_timeout;
+    if (looks_like_number $timeout_scale) {
+        $max_job_time *= $timeout_scale;
+        $max_upload_time *= $timeout_scale;
+        $upload_inactivity_timeout *= $timeout_scale;
+    }
+    return ($max_job_time, $max_setup_time, $max_upload_time, 
$upload_inactivity_timeout);
 }
 
 sub _handle_timeout ($self, $engine = undef) {
@@ -219,6 +229,53 @@
     $self->{_timeout_timer} = $loop->timer($timeout => sub { 
$self->_handle_timeout($engine) });
 }
 
+sub _set_upload_timeout ($self, $timeout = undef) {
+    $timeout //= $self->{_max_upload_time} // 
(_compute_timeouts($self->settings // {}))[2];
+    my $loop = Mojo::IOLoop->singleton;
+    if (my $current_timer = $self->{_upload_timeout_timer}) { 
$loop->remove($current_timer) }
+    $self->{_upload_timeout_timer} = $loop->timer($timeout => sub { 
$self->_handle_upload_timeout });
+}
+
+sub _set_upload_inactivity_timeout ($self, $timeout = undef) {
+    $timeout //= $self->{_upload_inactivity_timeout} // 
(_compute_timeouts($self->settings // {}))[3];
+    my $loop = Mojo::IOLoop->singleton;
+    if (my $current_timer = $self->{_upload_inactivity_timer}) { 
$loop->remove($current_timer) }
+    $self->{_upload_inactivity_timer} = $loop->timer($timeout => sub { 
$self->_handle_upload_inactivity_timeout });
+}
+
+sub _reset_upload_inactivity_timeout ($self) {
+    return unless $self->status eq 'stopping';
+    $self->_set_upload_inactivity_timeout;
+}
+
+sub _terminate_upload_due_to_timeout ($self, $flag, $message) {
+    my $job_id = $self->id;
+    log_warning("Job $job_id stopped because $message");
+    $self->_remove_timer([qw(_upload_results_timer _timeout_timer 
_upload_timeout_timer _upload_inactivity_timer)]);
+    $self->{$flag} = 1;
+    if (my $proc = delete $self->{_upload_subprocess}) {
+        if (my $pid = $proc->pid) {
+            kill 'KILL', $pid;
+        }
+    }
+    if (my $proc = delete $self->{_upload_images_subprocess}) {
+        if (my $pid = $proc->pid) {
+            kill 'KILL', $pid;
+        }
+    }
+    $self->conclude_upload_if_ongoing;
+    $self->_stop_step_5_finalize(WORKER_SR_TIMEOUT, {result => 
TIMEOUT_EXCEEDED});
+}
+
+sub _handle_upload_timeout ($self) {
+    $self->_terminate_upload_due_to_timeout(_upload_timeout => 'upload 
exceeded MAX_UPLOAD_TIME');
+}
+
+sub _handle_upload_inactivity_timeout ($self) {
+    $self->_terminate_upload_due_to_timeout(
+        _upload_inactivity_timeout_exceeded => 'upload exceeded 
UPLOAD_INACTIVITY_TIMEOUT');
+}
+
 sub start ($self) {
     my $id = $self->id;
     my $info = $self->info;
@@ -263,7 +320,10 @@
     $ENV{OPENQA_SHAREDIR} = $client->working_directory;
 
     # stop setup if timeout has been exceeded
-    my ($max_job_time, $max_setup_time) = _compute_timeouts($job_settings);
+    my ($max_job_time, $max_setup_time, $max_upload_time, 
$upload_inactivity_timeout)
+      = _compute_timeouts($job_settings);
+    $self->{_max_upload_time} = $max_upload_time;
+    $self->{_upload_inactivity_timeout} = $upload_inactivity_timeout;
     $self->_set_timeout($max_setup_time);
 
     # start isotovideo
@@ -337,6 +397,8 @@
 
     $self->_set_status(stopping => {reason => $reason});
     $self->_remove_timer(['_timeout_timer']);
+    $self->_set_upload_timeout;
+    $self->_set_upload_inactivity_timeout;
 
     $self->_stop_step_2_post_status(
         $reason,
@@ -365,7 +427,10 @@
         json => {
             status => {uploading => 1, worker_id => $client->worker_id},
         },
-        callback => $callback,
+        callback => sub (@args) {
+            $self->_reset_upload_inactivity_timeout;
+            $callback->(@args);
+        },
     );
 }
 
@@ -374,7 +439,12 @@
     # skip if isotovideo not running anymore (e.g. when isotovideo just exited 
on its own)
     return Mojo::IOLoop->next_tick($callback) unless $self->is_backend_running;
 
-    $self->isotovideo_client->stop_gracefully($reason, $callback);
+    $self->isotovideo_client->stop_gracefully(
+        $reason,
+        sub (@args) {
+            $self->_reset_upload_inactivity_timeout;
+            $callback->(@args);
+        });
 }
 
 sub _redact_file ($file_path, $file_name) {
@@ -398,8 +468,10 @@
     # upload logs and assets
     return Mojo::IOLoop->next_tick(sub { $self->_stop_step_5_1_upload($reason, 
$callback) })
       if $reason eq WORKER_COMMAND_QUIT || $reason eq WORKER_COMMAND_ABORT || 
$reason eq WORKER_SR_API_FAILURE;
-    Mojo::IOLoop->subprocess(
-        sub {
+    my $upload_subprocess = $self->{_upload_subprocess} = 
Mojo::IOLoop->subprocess(
+        sub ($subprocess) {
+            local $self->{_current_subprocess} = $subprocess;
+
             # upload ulogs
             my @uploaded_logfiles = glob "$pooldir/ulogs/*";
             for my $file (@uploaded_logfiles) {
@@ -413,6 +485,7 @@
                     $reason = WORKER_SR_API_FAILURE;
                     last;
                 }
+                $subprocess->progress('file');
             }
 
             # upload assets created by successful jobs
@@ -429,6 +502,7 @@
                             asset => $dir,
                         );
                         last unless ($upload_result = 
$self->_upload_log_file_or_asset(\%upload_parameter));
+                        $subprocess->progress('file');
                     }
                     if (!$upload_result) {
                         $reason = WORKER_SR_API_FAILURE;
@@ -461,14 +535,19 @@
                     $reason = WORKER_SR_API_FAILURE;
                     last;
                 }
+                $subprocess->progress('file');
             }
             return $reason;
         },
         sub {
             my ($subprocess, $err, $reason) = @_;
+            delete $self->{_upload_subprocess};
+            return if $self->{_upload_timeout} || 
$self->{_upload_inactivity_timeout_exceeded};
+            $self->_reset_upload_inactivity_timeout;
             log_error("Upload subprocess error: $err") if $err;
             $self->_stop_step_5_1_upload($reason // WORKER_SR_API_FAILURE, 
$callback);
         });
+    $upload_subprocess->on(progress => sub { 
$self->_reset_upload_inactivity_timeout });
 }
 
 sub _stop_step_5_1_upload ($self, $reason, $callback) {
@@ -478,11 +557,13 @@
     $self->emit(uploading_logs_and_assets_concluded => {});
     # ensure no asynchronous "side tasks" are started anymore automatically at 
this point
     $self->_remove_timer;
+    $self->_reset_upload_inactivity_timeout;
     # postpone any further actions until a possibly ongoing result upload has 
been concluded
     $self->_invoke_after_result_upload(sub { 
$self->_stop_step_5_2_upload($reason, $callback); });
 }
 
 sub _stop_step_5_2_upload ($self, $reason, $callback) {
+    $self->_reset_upload_inactivity_timeout;
 
     # do final status upload for selected reasons
     my $job_id = $self->id;
@@ -556,8 +637,12 @@
 
 sub _format_reason ($self, $state, $result, $reason) {
     # format stop reasons from the worker itself
-    return 'timeout: ' . ($self->{_engine} ? 'test execution exceeded 
MAX_JOB_TIME' : 'setup exceeded MAX_SETUP_TIME')
-      if $reason eq WORKER_SR_TIMEOUT;
+    if ($reason eq WORKER_SR_TIMEOUT) {
+        return 'timeout: upload exceeded UPLOAD_INACTIVITY_TIMEOUT' if 
$self->{_upload_inactivity_timeout_exceeded};
+        return 'timeout: upload exceeded MAX_UPLOAD_TIME' if 
$self->{_upload_timeout};
+        return 'timeout: '
+          . ($self->{_engine} ? 'test execution exceeded MAX_JOB_TIME' : 
'setup exceeded MAX_SETUP_TIME');
+    }
     return "$self->{_setup_error_category}: $self->{_setup_error}" if $reason 
eq WORKER_SR_SETUP_FAILURE;
     if ($reason eq WORKER_SR_API_FAILURE) {
         my $last_client_error = $self->client->last_error;
@@ -704,6 +789,7 @@
 }
 
 sub _conclude_upload ($self, $callback = undef, $result = {}) {
+    $self->_reset_upload_inactivity_timeout if $self->status eq 'stopping';
     my $existing_callback = delete $self->{_upload_results_callback};
     $callback //= $existing_callback;
     $self->{_is_uploading_results} = 0;
@@ -832,6 +918,7 @@
         \%status,
         sub {
             my ($status_post_res) = @_;
+            $self->_reset_upload_inactivity_timeout;
 
             # handle error occurred when posting the status
             if (!$status_post_res) {
@@ -922,8 +1009,14 @@
 }
 
 sub _upload_results_step_2_upload_images ($self, $callback) {
-    Mojo::IOLoop->subprocess(sub { 
$self->_upload_results_step_2_1_upload_images },
-        sub ($subprocess, $error, @args) { 
$self->_upload_results_step_2_2_upload_images($callback, $error) });
+    $self->{_upload_images_subprocess} = Mojo::IOLoop->subprocess(
+        sub { $self->_upload_results_step_2_1_upload_images },
+        sub ($subprocess, $error, @args) {
+            delete $self->{_upload_images_subprocess};
+            return if $self->{_upload_timeout} || 
$self->{_upload_inactivity_timeout_exceeded};
+            $self->_reset_upload_inactivity_timeout;
+            $self->_upload_results_step_2_2_upload_images($callback, $error);
+        });
 }
 
 sub _upload_results_step_3_finalize ($self, $upload_up_to, $callback) {
@@ -1006,6 +1099,7 @@
     my $retries = $global_settings->{UPLOAD_RETRIES} // 10;
     my $local_upload = $global_settings->{LOCAL_UPLOAD} // 1;
     my $ua = $self->client->ua;
+    my $subprocess = $self->{_current_subprocess};
     my @channels_worker_only = ('worker');
     my @channels_both = qw(autoinst worker);
     my $error;
@@ -1016,6 +1110,7 @@
         'upload_local.prepare' => sub ($upload) {
             log_info("$filename: local upload (no chunks needed)", channels => 
\@channels_worker_only, default => 1);
             chmod DOWNLOAD_PERMISSION, $file;
+            $subprocess->progress('local') if $subprocess;
         });
     $ua->upload->once(
         'upload_chunk.prepare' => sub ($upload, $pieces) {
@@ -1037,6 +1132,7 @@
                 channels => ($show_in_autoinst_log ? \@channels_both : 
\@channels_worker_only),
                 default => 1
             );
+            $subprocess->progress('chunk') if $subprocess;
         });
 
     my $response_cb = sub ($upload, $tx, $retries) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/t/03-auth-openid.t 
new/openQA-5.1793722468.aa0c30fc/t/03-auth-openid.t
--- old/openQA-5.1793632686.cbcf1318/t/03-auth-openid.t 2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/t/03-auth-openid.t 2026-11-03 
17:14:28.000000000 +0100
@@ -34,13 +34,32 @@
 
 my $params = Test::MockObject->new->set_always(pairs => ['openid.op_endpoint', 
'https://www.opensuse.org/openid/']);
 my $req = Test::MockObject->new->set_always(params => $params)->set_always(url 
=> $url);
-my $log = Test::MockObject->new->set_true('error');
+my $log = Test::MockObject->new->set_true('error')->set_true('debug');
 my $app = Test::MockObject->new->set_always(config => {})->set_always(log => 
$log);
 $c->set_always(return_page => undef)->set_always(req => $req)->set_always(app 
=> $app)->set_true('flash');
 Test::MockObject->new->set_always(params => $params)->set_always(url => $url);
 is +OpenQA::WebAPI::Auth::OpenID::auth_response($c), 0, 'can call 
auth_response';
 $c->app->log->called_ok('error', 'an error was logged for call without proper 
config');
 
+my $empty_params = Test::MockObject->new->set_always(pairs => []);
+$req->set_always(params => $empty_params);
+$log->set_true('info');
+$log->clear;
+$c->clear;
+is +OpenQA::WebAPI::Auth::OpenID::auth_response($c), 0, 'can call 
auth_response without OpenID parameters';
+$c->app->log->called_ok('info', 'info logged when response invoked without 
OpenID parameters');
+ok !$c->app->log->called('error'), 'no error logged for direct visit without 
OpenID parameters';
+ok !$c->called('flash'), 'no flash message shown for direct visit without 
OpenID parameters';
+
+my $missing_endpoint_params = Test::MockObject->new->set_always(pairs => 
['openid.foo', 'bar']);
+$req->set_always(params => $missing_endpoint_params);
+$log->clear;
+$c->clear;
+is +OpenQA::WebAPI::Auth::OpenID::auth_response($c), 0,
+  'can call auth_response with OpenID parameters lacking endpoint';
+$c->app->log->called_ok('error', 'an error was logged when OpenID response 
lacks provider endpoint');
+$c->called_ok('flash', 'flash message shown when OpenID response lacks 
provider endpoint');
+
 my $mock_openid_consumer = Test::MockModule->new('Net::OpenID::Consumer');
 $mock_openid_consumer->redefine(
     handle_server_response => sub ($self, %res_handlers) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/t/24-worker-jobs.t 
new/openQA-5.1793722468.aa0c30fc/t/24-worker-jobs.t
--- old/openQA-5.1793632686.cbcf1318/t/24-worker-jobs.t 2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/t/24-worker-jobs.t 2026-11-03 
17:14:28.000000000 +0100
@@ -28,9 +28,10 @@
 use Mojo::UserAgent;
 use Mojo::URL;
 use Mojo::IOLoop;
-use OpenQA::Constants qw(DEFAULT_MAX_JOB_TIME DEFAULT_MAX_SETUP_TIME 
DEFAULT_UPLOAD_CHUNK_SIZE WORKER_COMMAND_CANCEL
-  WORKER_COMMAND_QUIT WORKER_COMMAND_OBSOLETE WORKER_SR_SETUP_FAILURE 
WORKER_SR_TIMEOUT WORKER_EC_ASSET_FAILURE
-  WORKER_EC_CACHE_FAILURE WORKER_SR_API_FAILURE WORKER_SR_DIED WORKER_SR_DONE 
WORKER_SR_BROKEN);
+use OpenQA::Constants qw(DEFAULT_MAX_JOB_TIME DEFAULT_MAX_SETUP_TIME 
DEFAULT_MAX_UPLOAD_TIME
+  DEFAULT_UPLOAD_INACTIVITY_TIMEOUT DEFAULT_UPLOAD_CHUNK_SIZE 
WORKER_COMMAND_CANCEL WORKER_COMMAND_QUIT
+  WORKER_COMMAND_OBSOLETE WORKER_SR_SETUP_FAILURE WORKER_SR_TIMEOUT 
WORKER_EC_ASSET_FAILURE WORKER_EC_CACHE_FAILURE
+  WORKER_SR_API_FAILURE WORKER_SR_DIED WORKER_SR_DONE WORKER_SR_BROKEN);
 use OpenQA::Worker::Job;
 use OpenQA::Worker::Settings;
 use OpenQA::Test::FakeWebSocketTransaction;
@@ -1432,23 +1433,38 @@
     #       uploaded results for might still be in progress and still needs to 
be considered on further uploads.
 };
 
-subtest 'computing max job time and max setup time' => sub {
+subtest 'computing timeouts' => sub {
     my %settings;
-    my ($max_job_time, $max_setup_time) = 
OpenQA::Worker::Job::_compute_timeouts(\%settings);
+    my ($max_job_time, $max_setup_time, $max_upload_time, 
$upload_inactivity_timeout)
+      = OpenQA::Worker::Job::_compute_timeouts(\%settings);
     is $max_job_time, DEFAULT_MAX_JOB_TIME, 'default scenario: default max job 
time)';
     is $max_setup_time, DEFAULT_MAX_SETUP_TIME, 'default scenario: default max 
setup time)';
+    is $max_upload_time, DEFAULT_MAX_UPLOAD_TIME, 'default scenario: default 
max upload time';
+    is $upload_inactivity_timeout, DEFAULT_UPLOAD_INACTIVITY_TIMEOUT,
+      'default scenario: default upload inactivity timeout';
 
     $settings{MAX_JOB_TIME} = sprintf '%d', DEFAULT_MAX_JOB_TIME / 2;
     $settings{MAX_SETUP_TIME} = sprintf '%d', DEFAULT_MAX_SETUP_TIME / 3;
-    ($max_job_time, $max_setup_time) = 
OpenQA::Worker::Job::_compute_timeouts(\%settings);
+    $settings{MAX_UPLOAD_TIME} = sprintf '%d', DEFAULT_MAX_UPLOAD_TIME / 4;
+    $settings{UPLOAD_INACTIVITY_TIMEOUT} = sprintf '%d', 
DEFAULT_UPLOAD_INACTIVITY_TIMEOUT / 5;
+    ($max_job_time, $max_setup_time, $max_upload_time, 
$upload_inactivity_timeout)
+      = OpenQA::Worker::Job::_compute_timeouts(\%settings);
     is $max_job_time, DEFAULT_MAX_JOB_TIME / 2, 'short scenario: max job time 
taken from settings';
     is $max_setup_time, DEFAULT_MAX_SETUP_TIME / 3, 'short scenario: max setup 
time taken from settings';
+    is $max_upload_time, DEFAULT_MAX_UPLOAD_TIME / 4, 'short scenario: max 
upload time taken from settings';
+    is $upload_inactivity_timeout, DEFAULT_UPLOAD_INACTIVITY_TIMEOUT / 5,
+      'short scenario: upload inactivity timeout taken from settings';
 
     $settings{TIMEOUT_SCALE} = '4';
     delete $settings{MAX_SETUP_TIME};
-    ($max_job_time, $max_setup_time) = 
OpenQA::Worker::Job::_compute_timeouts(\%settings);
+    delete $settings{MAX_UPLOAD_TIME};
+    delete $settings{UPLOAD_INACTIVITY_TIMEOUT};
+    ($max_job_time, $max_setup_time, $max_upload_time, 
$upload_inactivity_timeout)
+      = OpenQA::Worker::Job::_compute_timeouts(\%settings);
     is $max_job_time, DEFAULT_MAX_JOB_TIME * 2, 'max job time scaled';
     is $max_setup_time, DEFAULT_MAX_SETUP_TIME, 'max setup time not scaled';
+    is $max_upload_time, DEFAULT_MAX_UPLOAD_TIME * 4, 'max upload time scaled';
+    is $upload_inactivity_timeout, DEFAULT_UPLOAD_INACTIVITY_TIMEOUT * 4, 
'upload inactivity timeout scaled';
     is_deeply [sort keys %settings], [qw(MAX_JOB_TIME TIMEOUT_SCALE)], 'no 
extra settings added so far';
 };
 
@@ -1465,6 +1481,88 @@
       'timeout logged';
 };
 
+subtest 'handling upload timeout' => sub {
+    my ($job, $event_data) = OpenQA::Worker::Job->new($worker, $client, {id => 
2, URL => $engine_url});
+    $job->_set_status(stopping => {reason => WORKER_SR_DONE});
+    $job->on(status_changed => sub ($job, $data) { $event_data = $data });
+
+    my $proc = Test::MockObject->new->set_always(pid => 1_234_567);
+    $job->{_upload_subprocess} = $proc;
+    my $img_proc = Test::MockObject->new->set_always(pid => 1_234_568);
+    $job->{_upload_images_subprocess} = $img_proc;
+    $job->{_is_uploading_results} = 1;
+
+    combined_like {
+        $job->_handle_upload_timeout;
+        wait_until_job_status_ok($job, 'stopped');
+    }
+    qr/stopped because upload exceeded MAX_UPLOAD_TIME/, 'upload timeout 
logged';
+
+    is $job->status, 'stopped', 'job is stopped';
+    is $event_data->{reason}, WORKER_SR_TIMEOUT, 'stop reason is timeout';
+    ok !defined $job->{_upload_subprocess}, 'upload subprocess cleaned up';
+    ok !defined $job->{_upload_images_subprocess}, 'upload images subprocess 
cleaned up';
+    ok !$job->is_uploading_results, 'ongoing upload concluded';
+    is $job->_format_reason(undef, TIMEOUT_EXCEEDED, WORKER_SR_TIMEOUT),
+      'timeout: upload exceeded MAX_UPLOAD_TIME', 'format reason for upload 
timeout';
+
+    subtest 'test inactivity timeout' => sub {
+        ($job, $event_data) = OpenQA::Worker::Job->new($worker, $client, {id 
=> 3, URL => $engine_url});
+        $job->_set_status(stopping => {reason => WORKER_SR_DONE});
+        $job->on(status_changed => sub ($job, $data) { $event_data = $data });
+        $job->{_upload_subprocess} = Test::MockObject->new->set_always(pid => 
1_234_569);
+        $job->{_is_uploading_results} = 1;
+
+        combined_like {
+            $job->_handle_upload_inactivity_timeout;
+            wait_until_job_status_ok($job, 'stopped');
+        }
+        qr/stopped because upload exceeded UPLOAD_INACTIVITY_TIMEOUT/, 'upload 
inactivity timeout logged';
+
+        is $job->status, 'stopped', 'job is stopped';
+        is $event_data->{reason}, WORKER_SR_TIMEOUT, 'stop reason is timeout';
+        ok !defined $job->{_upload_subprocess}, 'upload subprocess cleaned up';
+        ok !$job->is_uploading_results, 'ongoing upload concluded';
+        is $job->_format_reason(undef, TIMEOUT_EXCEEDED, WORKER_SR_TIMEOUT),
+          'timeout: upload exceeded UPLOAD_INACTIVITY_TIMEOUT', 'format reason 
for upload inactivity timeout';
+    };
+
+    subtest 'test reset inactivity timeout' => sub {
+        my $job4 = OpenQA::Worker::Job->new($worker, $client, {id => 4, URL => 
$engine_url});
+        $job4->_set_status(stopping => {reason => WORKER_SR_DONE});
+        $job4->_set_upload_inactivity_timeout(100);
+        my $old_timer = $job4->{_upload_inactivity_timer};
+        ok $old_timer, 'inactivity timer set';
+        $job4->_reset_upload_inactivity_timeout;
+        my $new_timer = $job4->{_upload_inactivity_timer};
+        ok $new_timer, 'inactivity timer reset';
+        isnt $new_timer, $old_timer, 'timer replaced with new one';
+        $job4->_set_status(stopped => {});
+        ok !defined $job4->{_upload_inactivity_timer}, 'timer removed when 
stopped';
+    };
+
+    subtest 'progress reporting' => sub {
+        $job_mock->redefine(_stop_step_5_1_upload => sub ($self, $reason, 
$callback) { $callback->() });
+        $job_mock->redefine(_upload_log_file_or_asset => 1);
+
+        my $job = OpenQA::Worker::Job->new($worker, $client, {id => 100, URL 
=> $engine_url});
+        my $callback = sub ($res = {}) { $job->_set_status(stopped => $res) };
+        my $fake_process;
+        $job->_set_status(stopping => {reason => WORKER_SR_DONE});
+        path($worker->pool_directory, 'ulogs')->make_path->child('foo')->touch;
+        combined_like {
+            $job->_stop_step_4_upload(test => $callback);
+            wait_until_job_status_ok($job, 'stopped');
+        }
+        qr/Result: test/, 'result logged';
+        ok $fake_process = OpenQA::Test::Utils::last_fake_process, 'process 
created' or return;
+        is_deeply $fake_process->progress_args, [(['file']) x 4], 'process 
reported'
+          or always_explain $fake_process->progress_args;
+
+        $job_mock->unmock($_) for qw(_stop_step_5_1_upload 
_upload_log_file_or_asset);
+    };
+};
+
 subtest 'ignoring known images and files' => sub {
     my $job = OpenQA::Worker::Job->new($worker, $client, {id => 1, URL => 
$engine_url});
     $job->{_images_to_send} = {md5sum1 => 1, md5sum2 => 1};
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/t/dummy-isotovideo.sh 
new/openQA-5.1793722468.aa0c30fc/t/dummy-isotovideo.sh
--- old/openQA-5.1793632686.cbcf1318/t/dummy-isotovideo.sh      2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/t/dummy-isotovideo.sh      2026-11-03 
17:14:28.000000000 +0100
@@ -2,6 +2,11 @@
 set -euo pipefail
 
 echo "dummy isotovideo started with arguments: $*"
+if [[ "$*" =~ --version ]]; then
+    echo "dummy isotovideo interface v0"
+    exit 0
+fi
+
 echo "arguments: $*" > autoinst-log.txt
 
 # create fake test results so the web UI thinks we ran at least one test 
module successfully and considers
@@ -9,7 +14,8 @@
 mkdir -p testresults
 echo '[{"category":"dummy","name":"fake","script":"none","flags":{}}]' > 
testresults/test_order.json
 echo '{"dents":0,"details":[],"result":"ok"}' > testresults/result-fake.json
-echo '{"current_test":"fake","status":"finished","test_execution_paused":0}' > 
autoinst-status.json
+echo '{"current_test":"fake","status":"finished","test_execution_paused":0}' > 
autoinst-status.json.tmp
+mv autoinst-status.json{.tmp,}
 
 # also add a vars.json file for less distracting error messages in the test log
 echo '{"ARCH":"x86_64","NAME":"fake-test"}' > vars.json
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/openQA-5.1793632686.cbcf1318/t/lib/OpenQA/Test/Utils.pm 
new/openQA-5.1793722468.aa0c30fc/t/lib/OpenQA/Test/Utils.pm
--- old/openQA-5.1793632686.cbcf1318/t/lib/OpenQA/Test/Utils.pm 2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/t/lib/OpenQA/Test/Utils.pm 2026-11-03 
17:14:28.000000000 +0100
@@ -595,19 +595,33 @@
     return $dest;
 }
 
+my @FAKE_PROCESSES;
+
+package Test::FakeSubprocess {
+    use Mojo::Base 'Mojo::EventEmitter', -signatures;
+    has pid => 12345;
+    has progress_args => sub { [] };
+    sub progress ($self, @args) { push @{$self->progress_args}, [@args]; 
$self->emit(progress => @args) }
+}
+
 sub mock_io_loop (%args) {
     my $io_loop_mock = Test::MockModule->new('Mojo::IOLoop');
     $io_loop_mock->redefine(    # avoid forking to prevent coverage analysis 
from slowing down the test significantly
         subprocess => sub ($io_loop, $function, $callback) {
+            my $proc = Test::FakeSubprocess->new;
             my @result;
             my $error = '';
-            try { @result = $function->() }
+            push @FAKE_PROCESSES, $proc;
+            try { @result = $function->($proc) }
             catch ($e) { $error = $e }
-            $io_loop->next_tick(sub { $callback->(undef, $error, @result) });
+            $io_loop->next_tick(sub { $callback->($proc, $error, @result) });
+            return $proc;
         }) if $args{subprocess};
     return $io_loop_mock;
 }
 
+sub last_fake_process () { pop @FAKE_PROCESSES }
+
 sub assume_all_assets_exist () { 
OpenQA::Schema->singleton->resultset('Assets')->search({})->update({size => 0}) 
}
 
 sub schedule_iso ($t, $args, $status = 200, $query_params = {}, $msg = undef) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/openQA-5.1793632686.cbcf1318/tools/ci/autoinst.sha 
new/openQA-5.1793722468.aa0c30fc/tools/ci/autoinst.sha
--- old/openQA-5.1793632686.cbcf1318/tools/ci/autoinst.sha      2026-11-02 
16:18:06.000000000 +0100
+++ new/openQA-5.1793722468.aa0c30fc/tools/ci/autoinst.sha      2026-11-03 
17:14:28.000000000 +0100
@@ -1 +1 @@
-5.044000-d65739f82b142e3ea7c26d7b873ec5b80361ca8c
\ No newline at end of file
+5.044000-90d5c840a113cf5cc3d3d1618a217771f5e6f4ba
\ No newline at end of file

++++++ openQA.obsinfo ++++++
--- /var/tmp/diff_new_pack.hTaxCs/_old  2026-11-04 19:44:50.594862604 +0100
+++ /var/tmp/diff_new_pack.hTaxCs/_new  2026-11-04 19:44:50.599862813 +0100
@@ -1,5 +1,5 @@
 name: openQA
-version: 5.1793632686.cbcf1318
-mtime: 1793632686
-commit: cbcf131875f9e4964fe8e0270befea922e00cb24
+version: 5.1793722468.aa0c30fc
+mtime: 1793722468
+commit: aa0c30fcbc07ef6ddb3746ad0bd3a83336b09a48
 

Reply via email to