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-10-16 20:10:34 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openQA (Old) and /work/SRC/openSUSE:Factory/.openQA.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openQA" Fri Oct 16 20:10:34 2026 rev:891 rq:1385184 version:5.1792132198.63d05d40 Changes: -------- --- /work/SRC/openSUSE:Factory/openQA/openQA.changes 2026-10-15 12:20:37.307277286 +0200 +++ /work/SRC/openSUSE:Factory/.openQA.new.3698736/openQA.changes 2026-10-16 20:14:18.215803517 +0200 @@ -1,0 +2,20 @@ +Fri Oct 16 06:30:10 UTC 2026 - [email protected] + +- Update to version 5.1792132198.63d05d40: + * refactor(ui): Initialise host previous jobs table directly + * fix(ui): Sort host previous jobs slot by worker instance + * fix(ui): Align previous jobs sort columns for host + * chore(deps): Dependency cron 2026-10-16 + * feat(systemd): Ignore try-reload-or-restart fails for workers + * chore(deps-dev): bump brace-expansion from 5.0.7 to 5.0.12 (#7949) + * chore(deps): bump source-map-js from 1.2.1 to 1.2.2 (#7948) + * chore(deps): bump postcss-selector-parser from 7.1.4 to 7.1.6 (#7947) + * fix(worker): Avoid concurrent result uploads + +------------------------------------------------------------------- +Thu Oct 15 05:07:03 UTC 2026 - [email protected] + +- Update to version 5.1792040810.42404b37: + * chore(deps): Dependency cron 2026-10-15 + +------------------------------------------------------------------- Old: ---- openQA-5.1792016843.dbd2549e.obscpio New: ---- openQA-5.1792132198.63d05d40.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openQA-client-test.spec ++++++ --- /var/tmp/diff_new_pack.Td4Bic/_old 2026-10-16 20:14:21.129925893 +0200 +++ /var/tmp/diff_new_pack.Td4Bic/_new 2026-10-16 20:14:21.130925935 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-client Name: %{short_name}-test -Version: 5.1792016843.dbd2549e +Version: 5.1792132198.63d05d40 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-test.spec ++++++ --- /var/tmp/diff_new_pack.Td4Bic/_old 2026-10-16 20:14:21.160927194 +0200 +++ /var/tmp/diff_new_pack.Td4Bic/_new 2026-10-16 20:14:21.162927278 +0200 @@ -18,7 +18,7 @@ %define short_name openQA Name: %{short_name}-test -Version: 5.1792016843.dbd2549e +Version: 5.1792132198.63d05d40 Release: 0 Summary: Test package for openQA License: GPL-2.0-or-later ++++++ openQA-worker-test.spec ++++++ --- /var/tmp/diff_new_pack.Td4Bic/_old 2026-10-16 20:14:21.191928496 +0200 +++ /var/tmp/diff_new_pack.Td4Bic/_new 2026-10-16 20:14:21.193928580 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-worker Name: %{short_name}-test -Version: 5.1792016843.dbd2549e +Version: 5.1792132198.63d05d40 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA.spec ++++++ --- /var/tmp/diff_new_pack.Td4Bic/_old 2026-10-16 20:14:21.225929924 +0200 +++ /var/tmp/diff_new_pack.Td4Bic/_new 2026-10-16 20:14:21.227930008 +0200 @@ -104,7 +104,7 @@ %define devel_requires %devel_no_selenium_requires chromedriver Name: openQA -Version: 5.1792016843.dbd2549e +Version: 5.1792132198.63d05d40 Release: 0 Summary: Framework for automated system-level testing (web-frontend, scheduler and tools) Group: Development/Tools/Other ++++++ node_modules.obscpio ++++++ Binary files old/brace-expansion-5.0.12.tgz and new/brace-expansion-5.0.12.tgz differ Binary files old/brace-expansion-5.0.7.tgz and new/brace-expansion-5.0.7.tgz differ Binary files old/postcss-selector-parser-7.1.4.tgz and new/postcss-selector-parser-7.1.4.tgz differ Binary files old/postcss-selector-parser-7.1.6.tgz and new/postcss-selector-parser-7.1.6.tgz differ Binary files old/source-map-js-1.2.1.tgz and new/source-map-js-1.2.1.tgz differ Binary files old/source-map-js-1.2.2.tgz and new/source-map-js-1.2.2.tgz differ ++++++ node_modules.spec.inc ++++++ --- /var/tmp/diff_new_pack.Td4Bic/_old 2026-10-16 20:14:21.968961127 +0200 +++ /var/tmp/diff_new_pack.Td4Bic/_new 2026-10-16 20:14:21.971961253 +0200 @@ -48,7 +48,7 @@ Source1047: https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz#/astral-regex-2.0.0.tgz Source1048: https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz#/balanced-match-4.0.4.tgz Source1049: https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz#/bootstrap-5.3.8.tgz -Source1050: https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz#/brace-expansion-5.0.7.tgz +Source1050: https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz#/brace-expansion-5.0.12.tgz Source1051: https://registry.npmjs.org/braces/-/braces-3.0.3.tgz#/braces-3.0.3.tgz Source1052: https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz#/cacheable-2.5.0.tgz Source1053: https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#/callsites-3.1.0.tgz @@ -203,7 +203,7 @@ Source1202: https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz#/postcss-resolve-nested-selector-0.1.6.tgz Source1203: https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz#/postcss-safe-parser-7.0.1.tgz Source1204: https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz#/postcss-scss-4.0.9.tgz -Source1205: https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz#/postcss-selector-parser-7.1.4.tgz +Source1205: https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz#/postcss-selector-parser-7.1.6.tgz Source1206: https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#/postcss-value-parser-4.2.0.tgz Source1207: https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#/prelude-ls-1.2.1.tgz Source1208: https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz#/prettier-3.9.6.tgz @@ -223,7 +223,7 @@ Source1222: https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz#/signal-exit-4.1.0.tgz Source1223: https://registry.npmjs.org/slash/-/slash-5.1.0.tgz#/slash-5.1.0.tgz Source1224: https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz#/slice-ansi-4.0.0.tgz -Source1225: https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz#/source-map-js-1.2.1.tgz +Source1225: https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz#/source-map-js-1.2.2.tgz Source1226: https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#/string-width-4.2.3.tgz Source1227: https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz#/string-width-8.2.2.tgz Source1228: https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#/strip-ansi-6.0.1.tgz ++++++ openQA-5.1792016843.dbd2549e.obscpio -> openQA-5.1792132198.63d05d40.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/assets/javascripts/admin_worker.js new/openQA-5.1792132198.63d05d40/assets/javascripts/admin_worker.js --- old/openQA-5.1792016843.dbd2549e/assets/javascripts/admin_worker.js 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/assets/javascripts/admin_worker.js 2026-10-16 08:29:58.000000000 +0200 @@ -26,7 +26,6 @@ } function setupWorkerNeedles() { - setupHostPreviousJobs(); const table = $('#previous_jobs').DataTable({ ajax: $('#previous_jobs').data('ajax-url'), deferRender: true, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm new/openQA-5.1792132198.63d05d40/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm --- old/openQA-5.1792016843.dbd2549e/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/lib/OpenQA/WebAPI/Controller/Admin/Workers.pm 2026-10-16 08:29:58.000000000 +0200 @@ -8,11 +8,12 @@ use OpenQA::WebAPI::ServerSideDataTable; use Scalar::Util 'looks_like_number'; -use constant PREVIOUS_JOBS_COLUMNS => ( - [qw(BUILD DISTRI VERSION FLAVOR ARCH)], - [qw(passed_module_count softfailed_module_count failed_module_count)], - qw(t_finished), -); +# SQL sort keys positionally aligned with the DataTable display columns of each view +use constant JOB_NAME_SORT_COLUMNS => [qw(BUILD DISTRI VERSION FLAVOR ARCH)]; +use constant RESULT_SORT_COLUMNS => [qw(passed_module_count softfailed_module_count failed_module_count)]; +use constant PREVIOUS_JOBS_COLUMNS => (JOB_NAME_SORT_COLUMNS, RESULT_SORT_COLUMNS, 't_finished'); +use constant PREVIOUS_HOST_JOBS_COLUMNS => + (JOB_NAME_SORT_COLUMNS, 'assigned_worker.instance', RESULT_SORT_COLUMNS, 't_finished'); sub _extend_info ($w) { my $info = $w->info; @@ -157,7 +158,7 @@ OpenQA::WebAPI::ServerSideDataTable::render_response( controller => $self, resultset => 'Jobs', - columns => [PREVIOUS_JOBS_COLUMNS], + columns => [PREVIOUS_HOST_JOBS_COLUMNS], initial_conds => [{assigned_worker_id => {-in => \@worker_ids}}], additional_params => {prefetch => [qw(children parents assigned_worker)]}, prepare_data_function => \&_previous_jobs_data, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/lib/OpenQA/Worker/Job.pm new/openQA-5.1792132198.63d05d40/lib/OpenQA/Worker/Job.pm --- old/openQA-5.1792016843.dbd2549e/lib/OpenQA/Worker/Job.pm 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/lib/OpenQA/Worker/Job.pm 2026-10-16 08:29:58.000000000 +0200 @@ -697,6 +697,10 @@ } sub _upload_results ($self, $callback) { + # avoid concurrent upload chains; the deferred call removes the timer set by the ongoing one + return $self->once(uploading_results_concluded => sub (@) { $self->_upload_results($callback) }) + if $self->{_is_uploading_results}; + # ensure an ongoing timer is cancelled in case upload_results has been called manually if (my $upload_results_timer = delete $self->{_upload_results_timer}) { Mojo::IOLoop->remove($upload_results_timer); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/package-lock.json new/openQA-5.1792132198.63d05d40/package-lock.json --- old/openQA-5.1792016843.dbd2549e/package-lock.json 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/package-lock.json 2026-10-16 08:29:58.000000000 +0200 @@ -738,16 +738,16 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/braces": { @@ -2605,9 +2605,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", - "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", + "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2856,9 +2856,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/systemd/[email protected] new/openQA-5.1792132198.63d05d40/systemd/[email protected] --- old/openQA-5.1792016843.dbd2549e/systemd/[email protected] 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/systemd/[email protected] 2026-10-16 08:29:58.000000000 +0200 @@ -3,5 +3,6 @@ [Service] Type=oneshot -ExecStart=/usr/bin/systemctl try-reload-or-restart openqa-worker-auto-restart@%i.service +# Ignore failures: systemctl exits 1 when an idle worker deactivates mid-reload +ExecStart=-/usr/bin/systemctl try-reload-or-restart openqa-worker-auto-restart@%i.service Slice=openqa-worker.slice diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/t/24-worker-jobs.t new/openQA-5.1792132198.63d05d40/t/24-worker-jobs.t --- old/openQA-5.1792016843.dbd2549e/t/24-worker-jobs.t 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/t/24-worker-jobs.t 2026-10-16 08:29:58.000000000 +0200 @@ -762,15 +762,26 @@ }, { json => { + outstanding_files => 0, + outstanding_images => 0, + upload_up_to => undef, + upload_up_to_current_module => undef + }, + path => '/liveviewhandler/api/v1/jobs/5/upload_progress' + }, + { + json => { + status => { + uploading => 1, + worker_id => 1 + } + }, + path => 'jobs/5/status' + }, + { + json => { status => { cmd_srv_url => $engine_url, - log => {}, - serial_log => {}, - serial_terminal => {}, - serial_terminal_user => { - 'data' => '', - 'offset' => (length $user_console_text) - }, test_execution_paused => 0, worker_hostname => '127.0.0.1', worker_id => 1 @@ -780,21 +791,24 @@ }, { json => { - outstanding_files => 0, - outstanding_images => 0, - upload_up_to => undef, - upload_up_to_current_module => undef + status => { + cmd_srv_url => $engine_url, + result => {}, + test_execution_paused => 0, + test_order => [], + worker_hostname => '127.0.0.1', + worker_id => 1 + } }, - path => '/liveviewhandler/api/v1/jobs/5/upload_progress' + path => 'jobs/5/status' }, - usual_status_updates(job_id => 5), { json => undef, path => 'jobs/5/set_done', worker_id => 1, } ], - 'expected REST-API calls happened' + 'expected REST-API calls happened, uploads serialized by the re-entrancy guard' or always_explain $client->sent_messages; $client->sent_messages([]); @@ -832,6 +846,89 @@ }; }; +subtest 'Avoiding concurrent result uploads' => sub { + my $client_mock = Test::MockModule->new('Test::FakeClient', no_auto => 1); + my @sent; + my @pending; + $client_mock->redefine( + send => sub ($self, $method, $path, %args) { + push @sent, $path; + push @pending, $args{callback} if $args{callback} && $args{callback} ne 'no'; + }); + my %timers; + my $next_timer_id = 1000; + $io_loop_mock->redefine( + timer => sub ($io, $delay, $cb) { + my $id = $next_timer_id++; + $timers{$id} = $cb; + return $id; + }, + remove => sub ($io, $id) { delete $timers{$id} }); + my $drain = sub { + Mojo::IOLoop->next_tick(sub { Mojo::IOLoop->stop }); + Mojo::IOLoop->start; + }; + + subtest 'calling _upload_results while an upload is in flight defers it until the ongoing one concludes' => sub { + @sent = (); + @pending = (); + %timers = (); + my $job = OpenQA::Worker::Job->new($worker, $client, {id => 8, URL => $engine_url}); + $job->{_engine} = {child => Test::FakeEngine->new}; + $job->_upload_results(sub { }); + is scalar(@pending), 1, 'ongoing upload posts jobs/8/status once and is in flight'; + my $deferred_invoked = 0; + $job->_upload_results(sub { ++$deferred_invoked }); + is scalar(@pending), 1, 'no second status post while an upload is in flight'; + ok $job->is_uploading_results, 'job still considered uploading results'; + shift(@pending)->({}); + $drain->(); + is scalar(@pending), 1, 'deferred upload posts status right after the ongoing one concluded'; + is scalar(@sent), 2, 'status posts are serialized instead of concurrent'; + shift(@pending)->({}); + $drain->(); + is $deferred_invoked, 1, 'callback of the deferred upload invoked after its upload concluded'; + is scalar(keys %timers), 1, 'exactly one upload timer active after both uploads concluded'; + ok defined $job->{_upload_results_timer}, 'job tracks the remaining upload timer'; + (values %timers)[0]->(); + is scalar(@pending), 1, 'remaining timer fires exactly one new upload'; + is scalar(@sent), 3, 'timer-triggered upload posts status'; + shift(@pending)->({}); + $drain->(); + is scalar(keys %timers), 1, 'still exactly one upload timer after the timer-triggered upload concluded'; + }; + + subtest 'engine startup with livelog viewer registered before the backend runs posts status only once at a time' => + sub { + @sent = (); + @pending = (); + %timers = (); + my $job = OpenQA::Worker::Job->new($worker, $client, {id => 9, URL => $engine_url}); + $job->start_livelog; + is $job->livelog_viewers, 1, 'viewer registered before the backend runs'; + is scalar(@pending), 0, 'no upload triggered while the backend is not running'; + $job->{_engine} = {child => Test::FakeEngine->new}; + Mojo::IOLoop->next_tick( + sub { + $job->_upload_results(sub { }); + }); + combined_like { $job->_add_livelog_viewers(0) } qr/Starting livelog/, 'livelog started with engine'; + is scalar(@pending), 1, 'livelog start triggers one status post'; + $drain->(); + is scalar(@pending), 1, 'next_tick upload is deferred, so never two concurrent status posts'; + is scalar(@sent), 1, 'only the livelog upload posted status so far'; + shift(@pending)->({}); + $drain->(); + is scalar(@sent), 2, 'deferred upload posts status after the ongoing one concluded'; + shift(@pending)->({}); + $drain->(); + is scalar(keys %timers), 1, 'exactly one upload timer remains after the startup scenario'; + }; + + $client_mock->unmock('send'); + $io_loop_mock->unmock('timer', 'remove'); +}; + subtest 'handling API failures' => sub { is_deeply $client->websocket_connection->sent_messages, [], 'no WebSocket calls yet'; is_deeply $client->sent_messages, [], 'no REST-API calls yet'; @@ -1046,7 +1143,7 @@ # assume the ongoing upload has concluded: this is supposed to continue stopping the job which is in turn # supposed to trigger the final upload - $job->emit(uploading_results_concluded => {}); + $job->conclude_upload_if_ongoing; wait_until_job_status_ok($job, 'stopped'); ok $final_result_upload_invoked, 'final result upload invoked'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/t/ui/15-admin-workers.t new/openQA-5.1792132198.63d05d40/t/ui/15-admin-workers.t --- old/openQA-5.1792016843.dbd2549e/t/ui/15-admin-workers.t 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/t/ui/15-admin-workers.t 2026-10-16 08:29:58.000000000 +0200 @@ -310,5 +310,75 @@ $w2->delete; }; +subtest 'host previous jobs table' => sub { + $driver->get('/admin/worker_hosts/localhost'); + wait_for_ajax; + my $table = $driver->find_element_by_id('previous_host_jobs'); + ok $table, 'host previous jobs table found'; + my @entries = map { $_->get_text() } $driver->find_child_elements($table, 'tbody/tr/td', 'xpath'); + is scalar @entries, 8, 'two previous jobs shown (4 cols per row)'; + is_deeply + \@entries, + [ + 'opensuse-13.1-NET-x86_64-Build0091-kde@64bit', + ':1', '', 'not yet', 'opensuse-Factory-staging_e-x86_64-Build87.5011-minimalx@32bit (restarted)', + ':1', '0', '1 hour ago', + ], + 'host previous jobs ordered by completion time descending by default'; + + $driver->find_element('#previous_host_jobs thead th:nth-child(4)')->click; + my @expected = ( + 'opensuse-Factory-staging_e-x86_64-Build87.5011-minimalx@32bit (restarted)', + ':1', '0', '1 hour ago', 'opensuse-13.1-NET-x86_64-Build0091-kde@64bit', + ':1', '', 'not yet', + ); + @entries = (); + wait_for { + @entries = map { $_->[0] } @{map_elements('#previous_host_jobs tbody tr td', 'e.textContent.trim()')}; + @entries == @expected && $entries[0] =~ /Factory/ && $entries[4] =~ /NET/ + } + 'host previous jobs sorted by completion time ascending'; + is_deeply \@entries, \@expected, 'host previous jobs sorted by completion time ascending after clicking column'; +}; + +subtest 'worker previous jobs table' => sub { + $driver->get('/admin/workers/1'); + wait_for_ajax; + my $table = $driver->find_element_by_id('previous_jobs'); + my @entries = map { $_->get_text() } $driver->find_child_elements($table, 'tbody/tr/td', 'xpath'); + is_deeply \@entries, + [ + 'opensuse-13.1-NET-x86_64-Build0091-kde@64bit', + '', 'not yet', 'opensuse-Factory-staging_e-x86_64-Build87.5011-minimalx@32bit (restarted)', + '0', '1 hour ago', + ], + 'worker previous jobs ordered by completion time descending by default'; +}; + +subtest 'host previous jobs slot column sorts by worker instance number' => sub { + my $w3 = $workers->create({id => 7, host => 'localhost', instance => 3}); + my $w4 = $workers->create({id => 9, host => 'localhost', instance => 2}); + $jobs->find(99963)->update({assigned_worker_id => $w3->id}); + $jobs->find(99961)->update({assigned_worker_id => $w4->id}); + + $driver->get('/admin/worker_hosts/localhost'); + wait_for_ajax; + $driver->find_element('#previous_host_jobs thead th:nth-child(2)')->click; + my @slots; + wait_for { + @slots + = map { $_->[0] } @{map_elements('#previous_host_jobs tbody tr td:nth-child(2)', 'e.textContent.trim()')}; + @slots == 3 && "@slots" eq ':1 :2 :3' + } + 'slot column sorted by worker instance number ascending'; + is_deeply \@slots, [':1', ':2', ':3'], + 'slot column sorted by worker instance number ascending rather than by worker id'; + + $jobs->find(99961)->update({assigned_worker_id => 1}); + $jobs->find(99963)->update({assigned_worker_id => undef}); + $w3->delete; + $w4->delete; +}; + kill_driver(); done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/templates/webapi/admin/workers/show_host.html.ep new/openQA-5.1792132198.63d05d40/templates/webapi/admin/workers/show_host.html.ep --- old/openQA-5.1792016843.dbd2549e/templates/webapi/admin/workers/show_host.html.ep 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/templates/webapi/admin/workers/show_host.html.ep 2026-10-16 08:29:58.000000000 +0200 @@ -7,7 +7,7 @@ cancel_url = "<%= url_for("apiv1_cancel", jobid => 'REPLACEIT') %>"; updateTimeago(); setupResultButtons(); - setupWorkerNeedles(); + setupHostPreviousJobs(); % end <div class="row"> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1792016843.dbd2549e/tools/ci/autoinst.sha new/openQA-5.1792132198.63d05d40/tools/ci/autoinst.sha --- old/openQA-5.1792016843.dbd2549e/tools/ci/autoinst.sha 2026-10-15 00:27:23.000000000 +0200 +++ new/openQA-5.1792132198.63d05d40/tools/ci/autoinst.sha 2026-10-16 08:29:58.000000000 +0200 @@ -1 +1 @@ -5.044000-3b85101553bc9ee20b29916a47d5ce879de2f2f1 \ No newline at end of file +5.044000-50bc19e891aec51e4f61cec73e78abb6b308ce4b \ No newline at end of file ++++++ openQA.obsinfo ++++++ --- /var/tmp/diff_new_pack.Td4Bic/_old 2026-10-16 20:14:33.064427372 +0200 +++ /var/tmp/diff_new_pack.Td4Bic/_new 2026-10-16 20:14:33.074427792 +0200 @@ -1,5 +1,5 @@ name: openQA -version: 5.1792016843.dbd2549e -mtime: 1792016843 -commit: dbd2549e6c3b22679edae4c011708893dc85fc7e +version: 5.1792132198.63d05d40 +mtime: 1792132198 +commit: 63d05d409d6cf5d2712ed3b2ea326913b7f90563
