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-31 15:29:00 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openQA (Old) and /work/SRC/openSUSE:Factory/.openQA.new.1999 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openQA" Tue Mar 31 15:29:00 2026 rev:831 rq:1343779 version:5.1774895777.6c2911d1 Changes: -------- --- /work/SRC/openSUSE:Factory/openQA/openQA.changes 2026-03-30 18:36:08.075187303 +0200 +++ /work/SRC/openSUSE:Factory/.openQA.new.1999/openQA.changes 2026-03-31 15:29:02.905172892 +0200 @@ -1,0 +2,17 @@ +Mon Mar 30 20:17:41 UTC 2026 - [email protected] + +- Update to version 5.1774895777.6c2911d1: + * feat: Customizable time_limit_days + * fix: arrow navigation not working in Chrome-based browsers + * style: Remove various magic numbers + * fix: ensure cache sync creates parent directory + * feat: Improve log message when sending command to ws server fails + * refactor: Use `any` instead of `grep` in worker schema + * refactor: Avoid repeated access to `$args{command}` in worker schema + * fix(clone-job): Restore authorized asset downloads after 06bc5193d25b + * fix: restore broken label and flag icons in comments + * feat: support parallel Perl tests with PROVE_JOBS in Makefile + * fix(ui): Prevent build tag labels from rendering in all caps + * feat: replace Test::Strict with Test::Compile in compilation check + +------------------------------------------------------------------- Old: ---- openQA-5.1774854217.24dbd811.obscpio New: ---- openQA-5.1774895777.6c2911d1.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openQA-client-test.spec ++++++ --- /var/tmp/diff_new_pack.FLRthc/_old 2026-03-31 15:29:06.221311013 +0200 +++ /var/tmp/diff_new_pack.FLRthc/_new 2026-03-31 15:29:06.249312179 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-client Name: %{short_name}-test -Version: 5.1774854217.24dbd811 +Version: 5.1774895777.6c2911d1 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-devel-test.spec ++++++ --- /var/tmp/diff_new_pack.FLRthc/_old 2026-03-31 15:29:06.533324008 +0200 +++ /var/tmp/diff_new_pack.FLRthc/_new 2026-03-31 15:29:06.541324342 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-devel Name: %{short_name}-test -Version: 5.1774854217.24dbd811 +Version: 5.1774895777.6c2911d1 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-test.spec ++++++ --- /var/tmp/diff_new_pack.FLRthc/_old 2026-03-31 15:29:06.889338837 +0200 +++ /var/tmp/diff_new_pack.FLRthc/_new 2026-03-31 15:29:06.933340669 +0200 @@ -18,7 +18,7 @@ %define short_name openQA Name: %{short_name}-test -Version: 5.1774854217.24dbd811 +Version: 5.1774895777.6c2911d1 Release: 0 Summary: Test package for openQA License: GPL-2.0-or-later ++++++ openQA-worker-test.spec ++++++ --- /var/tmp/diff_new_pack.FLRthc/_old 2026-03-31 15:29:07.177350833 +0200 +++ /var/tmp/diff_new_pack.FLRthc/_new 2026-03-31 15:29:07.189351333 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-worker Name: %{short_name}-test -Version: 5.1774854217.24dbd811 +Version: 5.1774895777.6c2911d1 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA.spec ++++++ --- /var/tmp/diff_new_pack.FLRthc/_old 2026-03-31 15:29:07.457362496 +0200 +++ /var/tmp/diff_new_pack.FLRthc/_new 2026-03-31 15:29:07.469362996 +0200 @@ -83,7 +83,7 @@ # Do not require on this in individual sub-packages except for the devel # package. # The following line is generated from dependencies.yaml -%define test_requires %common_requires %main_requires %mcp_requires %python_scripts_requires %worker_requires curl file jq openssh-common os-autoinst perl(App::cpanminus) perl(Selenium::Remote::Driver) >= 1.23 perl(Selenium::Remote::WDKeys) perl(Test::Exception) perl(Test::Fatal) perl(Test::Mock::Time) perl(Test::MockModule) perl(Test::MockObject) perl(Test::Mojo) perl(Test::Most) perl(Test::Output) perl(Test::Pod) perl(Test::Strict) perl(Test::Warnings) >= 0.029 postgresql-server >= 14 python3-setuptools +%define test_requires %common_requires %main_requires %mcp_requires %python_scripts_requires %worker_requires curl file jq openssh-common os-autoinst perl(App::cpanminus) perl(Selenium::Remote::Driver) >= 1.23 perl(Selenium::Remote::WDKeys) perl(Test::Compile) perl(Test::Exception) perl(Test::Fatal) perl(Test::Mock::Time) perl(Test::MockModule) perl(Test::MockObject) perl(Test::Mojo) perl(Test::Most) perl(Test::Output) perl(Test::Pod) perl(Test::Warnings) >= 0.029 postgresql-server >= 14 python3-setuptools %ifarch x86_64 %define qemu qemu qemu-kvm %else @@ -99,7 +99,7 @@ %define devel_requires %devel_no_selenium_requires chromedriver Name: openQA -Version: 5.1774854217.24dbd811 +Version: 5.1774895777.6c2911d1 Release: 0 Summary: The openQA web-frontend, scheduler and tools License: GPL-2.0-or-later ++++++ openQA-5.1774854217.24dbd811.obscpio -> openQA-5.1774895777.6c2911d1.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/Makefile new/openQA-5.1774895777.6c2911d1/Makefile --- old/openQA-5.1774854217.24dbd811/Makefile 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/Makefile 2026-03-30 20:36:17.000000000 +0200 @@ -18,6 +18,9 @@ EXTRA_PROVE_ARGS ?= # PROVE: Test application for Perl tests PROVE ?= prove +# Number of parallel jobs for prove +PROVE_JOBS ?= $(shell nproc 2>/dev/null || echo 1) +PROVE_JOBS_ARGS ?= -j$(PROVE_JOBS) ifeq ($(TESTS),) PROVE_ARGS ?= --trap -r ${EXTRA_PROVE_ARGS} t else @@ -280,7 +283,7 @@ export GLOBIGNORE="$(GLOBIGNORE)";\ export DEVEL_COVER_DB_FORMAT=JSON;\ export PERL5OPT="$(COVEROPT)$(PERL5OPT) -It/lib -I$(PWD)/t/lib -I$(PWD)/external/os-autoinst-common/lib $(CHECK_GIT_STATUS_OPT) -MOpenQA::Test::PatchDeparse";\ - RETRY=${RETRY} HEAVY=${HEAVY} FULLSTACK=${FULLSTACK} HOOK=./tools/delete-coverdb-folder timeout --foreground -s SIGINT -k 5 -v ${TIMEOUT_RETRIES} tools/retry "${PROVE}" ${PROVE_LIB_ARGS} ${PROVE_ARGS} + RETRY=${RETRY} HEAVY=${HEAVY} FULLSTACK=${FULLSTACK} HOOK=./tools/delete-coverdb-folder timeout --foreground -s SIGINT -k 5 -v ${TIMEOUT_RETRIES} tools/retry "${PROVE}" ${PROVE_LIB_ARGS} $(PROVE_JOBS_ARGS) ${PROVE_ARGS} .PHONY: setup-database setup-database: ## Set up the test database diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/assets/javascripts/keyevent.js new/openQA-5.1774895777.6c2911d1/assets/javascripts/keyevent.js --- old/openQA-5.1774854217.24dbd811/assets/javascripts/keyevent.js 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/assets/javascripts/keyevent.js 2026-03-30 20:36:17.000000000 +0200 @@ -2,8 +2,8 @@ // FIXME: key events may be different in other browsers: // http://www.javascripter.net/faq/keyeventconstantsfirefox.htm -if (typeof KeyEvent === 'undefined') { - const KeyEvent = { +if (typeof KeyEvent === 'undefined' || typeof KeyEvent.DOM_VK_LEFT === 'undefined') { + const keyEventDefaults = { DOM_VK_CANCEL: 3, DOM_VK_HELP: 6, DOM_VK_BACK_SPACE: 8, @@ -120,4 +120,9 @@ DOM_VK_QUOTE: 222, DOM_VK_META: 224 }; + if (typeof KeyEvent === 'undefined') { + KeyEvent = keyEventDefaults; + } else { + Object.assign(KeyEvent, keyEventDefaults); + } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/assets/javascripts/test_result.js new/openQA-5.1774895777.6c2911d1/assets/javascripts/test_result.js --- old/openQA-5.1774854217.24dbd811/assets/javascripts/test_result.js 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/assets/javascripts/test_result.js 2026-03-30 20:36:17.000000000 +0200 @@ -321,32 +321,32 @@ return; } - switch (e.which) { - case KeyEvent.DOM_VK_LEFT: + switch (e.key) { + case 'ArrowLeft': if (!e.shiftKey) { prevPreview(); e.preventDefault(); } break; - case KeyEvent.DOM_VK_RIGHT: + case 'ArrowRight': if (!e.shiftKey) { nextPreview(); e.preventDefault(); } break; - case KeyEvent.DOM_VK_ESCAPE: + case 'Escape': if (!e.shiftKey) { setCurrentPreview(null); e.preventDefault(); } break; - case KeyEvent.DOM_VK_UP: + case 'ArrowUp': if (e.shiftKey) { prevNeedle(); e.preventDefault(); } break; - case KeyEvent.DOM_VK_DOWN: + case 'ArrowDown': if (e.shiftKey) { nextNeedle(); e.preventDefault(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/assets/stylesheets/comments.scss new/openQA-5.1774895777.6c2911d1/assets/stylesheets/comments.scss --- old/openQA-5.1774854217.24dbd811/assets/stylesheets/comments.scss 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/assets/stylesheets/comments.scss 2026-03-30 20:36:17.000000000 +0200 @@ -68,7 +68,7 @@ font-style: normal; font-variant: normal; text-rendering: auto; - font-family: ForkAwesome; + font-family: 'Font Awesome 6 Free'; font-weight: 900; font-size: 0.8em; position: absolute; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/cpanfile new/openQA-5.1774895777.6c2911d1/cpanfile --- old/openQA-5.1774854217.24dbd811/cpanfile 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/cpanfile 2026-03-30 20:36:17.000000000 +0200 @@ -101,6 +101,7 @@ requires 'App::cpanminus'; requires 'Selenium::Remote::Driver', '>= 1.23'; requires 'Selenium::Remote::WDKeys'; + requires 'Test::Compile'; requires 'Test::Exception'; requires 'Test::Fatal'; requires 'Test::Mock::Time'; @@ -110,7 +111,6 @@ requires 'Test::Most'; requires 'Test::Output'; requires 'Test::Pod'; - requires 'Test::Strict'; requires 'Test::Warnings', '>= 0.029'; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/dependencies.yaml new/openQA-5.1774895777.6c2911d1/dependencies.yaml --- old/openQA-5.1774854217.24dbd811/dependencies.yaml 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/dependencies.yaml 2026-03-30 20:36:17.000000000 +0200 @@ -201,7 +201,7 @@ perl(Test::Exception): perl(Test::Mojo): perl(Test::Most): - perl(Test::Strict): + perl(Test::Compile): perl(Test::Fatal): perl(Test::MockModule): perl(Test::MockObject): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/dist/rpm/openQA.spec new/openQA-5.1774895777.6c2911d1/dist/rpm/openQA.spec --- old/openQA-5.1774854217.24dbd811/dist/rpm/openQA.spec 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/dist/rpm/openQA.spec 2026-03-30 20:36:17.000000000 +0200 @@ -83,7 +83,7 @@ # Do not require on this in individual sub-packages except for the devel # package. # The following line is generated from dependencies.yaml -%define test_requires %common_requires %main_requires %mcp_requires %python_scripts_requires %worker_requires curl file jq openssh-common os-autoinst perl(App::cpanminus) perl(Selenium::Remote::Driver) >= 1.23 perl(Selenium::Remote::WDKeys) perl(Test::Exception) perl(Test::Fatal) perl(Test::Mock::Time) perl(Test::MockModule) perl(Test::MockObject) perl(Test::Mojo) perl(Test::Most) perl(Test::Output) perl(Test::Pod) perl(Test::Strict) perl(Test::Warnings) >= 0.029 postgresql-server >= 14 python3-setuptools +%define test_requires %common_requires %main_requires %mcp_requires %python_scripts_requires %worker_requires curl file jq openssh-common os-autoinst perl(App::cpanminus) perl(Selenium::Remote::Driver) >= 1.23 perl(Selenium::Remote::WDKeys) perl(Test::Compile) perl(Test::Exception) perl(Test::Fatal) perl(Test::Mock::Time) perl(Test::MockModule) perl(Test::MockObject) perl(Test::Mojo) perl(Test::Most) perl(Test::Output) perl(Test::Pod) perl(Test::Warnings) >= 0.029 postgresql-server >= 14 python3-setuptools %ifarch x86_64 %define qemu qemu qemu-kvm %else diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/etc/openqa/openqa.ini new/openQA-5.1774895777.6c2911d1/etc/openqa/openqa.ini --- old/openQA-5.1774854217.24dbd811/etc/openqa/openqa.ini 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/etc/openqa/openqa.ini 2026-03-30 20:36:17.000000000 +0200 @@ -128,6 +128,9 @@ ## How many builds to show per job group on the web UI front page ## Can be overridden with the limit_builds query param #frontpage_builds = 3 +## Maximum age for the builds per job group on the web UI front page +## Can be overridden with the time_limit_days query param +#frontpage_time_limit_days = 14 ## List of hosts (space-separated) where scenario definitions specified via ## `SCENARIO_DEFINITIONS_YAML_FILE=https://…` are allowed to be downloaded from diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Assets.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Assets.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Assets.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Assets.pm 2026-03-30 20:36:17.000000000 +0200 @@ -13,12 +13,14 @@ use YAML::PP qw(LoadFile); use Feature::Compat::Try; +use constant ASSET_PACK_VERSION_NO_RETRY => 2.13; + sub setup ($server) { # setup asset pack, note that the config file is shared with tools/generate-packed-assets $server->plugin(AssetPack => LoadFile($server->home->child('assets', 'assetpack.yml'))); # The feature was added in the 2.14 release, the version check can be removed once openQA depends on a newer version - $server->asset->store->retries(5) if $Mojolicious::Plugin::AssetPack::VERSION > 2.13; + $server->asset->store->retries(5) if $Mojolicious::Plugin::AssetPack::VERSION > ASSET_PACK_VERSION_NO_RETRY; # -> read assets/assetpack.def local $SIG{CHLD}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/BuildResults.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/BuildResults.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/BuildResults.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/BuildResults.pm 2026-03-30 20:36:17.000000000 +0200 @@ -14,6 +14,8 @@ use Sort::Versions; use Time::Seconds; +use constant DEFAULT_BUILD_RESULTS_LIMIT => 400; + sub init_job_figures ($job_result) { # relevant distributions for the build (hash is used as a set) $job_result->{distris} = {}; @@ -123,7 +125,7 @@ my $sort_column = $buildver_sort_mode == BUILD_SORT_BY_OLDEST_JOB ? 'oldest_job' : 'newest_job'; # 400 is the max. limit selectable in the group overview - my $row_limit = (defined($limit) && $limit > 400) ? $limit : 400; + my $row_limit = (defined($limit) && $limit > DEFAULT_BUILD_RESULTS_LIMIT) ? $limit : DEFAULT_BUILD_RESULTS_LIMIT; my @search_cols = qw(VERSION BUILD); my %search_opts = ( select => [@search_cols, {max => 'id', -as => 'newest_job'}, {min => 'id', -as => 'oldest_job'}], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/CacheService/Response.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/CacheService/Response.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/CacheService/Response.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/CacheService/Response.pm 2026-03-30 20:36:17.000000000 +0200 @@ -4,6 +4,8 @@ package OpenQA::CacheService::Response; use Mojo::Base -base, -signatures; +use constant MAX_INACTIVE_JOBS_OFFSET => 40; + has [qw(data error)]; # define soft-limit for inactive Minion jobs (enforced when determining idle worker slots availability) @@ -11,7 +13,7 @@ # define hard-limit for inactive Minion jobs (enforced on setup of already started openQA job) has max_inactive_jobs_hard_limit => sub ($self) { - $ENV{OPENQA_CACHE_MAX_INACTIVE_JOBS_HARD_LIMIT} // ($self->max_inactive_jobs + 40); + $ENV{OPENQA_CACHE_MAX_INACTIVE_JOBS_HARD_LIMIT} // ($self->max_inactive_jobs + MAX_INACTIVE_JOBS_OFFSET); }; sub has_error ($self) { !!$self->error } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/CacheService/Task/Sync.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/CacheService/Task/Sync.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/CacheService/Task/Sync.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/CacheService/Task/Sync.pm 2026-03-30 20:36:17.000000000 +0200 @@ -4,6 +4,7 @@ package OpenQA::CacheService::Task::Sync; use Mojo::Base 'Mojolicious::Plugin', -signatures; use OpenQA::Task::SignalGuard; +use Mojo::File 'path'; use Mojo::URL; use Time::Seconds; @@ -35,6 +36,7 @@ my $ctx = $app->log->context("[#$job_id]"); $ctx->info(qq{Sync: "$from" to "$to"}); + path($to)->make_path; my @cmd = (qw(rsync -avHP --timeout), RSYNC_TIMEOUT, "$from/", qw(--delete), "$to/tests/"); my $cmd = join ' ', @cmd; my $status; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/CacheService.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/CacheService.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/CacheService.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/CacheService.pm 2026-03-30 20:36:17.000000000 +0200 @@ -6,6 +6,7 @@ use Mojo::SQLite; use Mojo::File 'path'; +use Time::Seconds; use OpenQA::Worker::Settings; use OpenQA::CacheService::Model::Cache; use OpenQA::CacheService::Model::Downloads; @@ -125,7 +126,7 @@ local $ENV{MOJO_LOG_SHORT} = 1; my $app = __PACKAGE__->new; - $ENV{MOJO_INACTIVITY_TIMEOUT} //= 300; + $ENV{MOJO_INACTIVITY_TIMEOUT} //= ONE_MINUTE * 5; $app->log->debug("Starting cache service: $0 @args"); $app->defaults->{service_pid} = $$; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Client/Archive.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Client/Archive.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Client/Archive.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Client/Archive.pm 2026-03-30 20:36:17.000000000 +0200 @@ -10,6 +10,8 @@ use Mojo::JSON 'encode_json'; use HTTP::Status qw(:constants); +use constant DEFAULT_MAX_ASSET_SIZE => 1024 * 1024 * 200; + sub run ($self, $options) { croak 'Options must be a HASH ref' unless ref $options eq 'HASH'; croak 'Need a URL to download job information' unless $options->{url}; @@ -17,8 +19,7 @@ my $url = Mojo::URL->new($options->{url}); my $req = $self->client->get($url); my $res = $req->res; - my $default_max_asset_size = 1024 * 1024 * 200; - $options->{'asset-size-limit'} //= $default_max_asset_size; + $options->{'asset-size-limit'} //= DEFAULT_MAX_ASSET_SIZE; $self->client->max_response_size($options->{'asset-size-limit'}); my $code = $res->code; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Client/Upload.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Client/Upload.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Client/Upload.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Client/Upload.pm 2026-03-30 20:36:17.000000000 +0200 @@ -10,6 +10,8 @@ use Mojo::File qw(path); use Feature::Compat::Try; +use constant DEFAULT_CHUNK_SIZE => 1_000_000; + sub _upload_asset_fail ($self, $uri, $form) { $form->{state} = 'fail'; return $self->client->start($self->_build_post("$uri/upload_state" => $form)); @@ -38,7 +40,7 @@ return undef; } - my $chunk_size = $opts->{chunk_size} // 1000000; + my $chunk_size = $opts->{chunk_size} // DEFAULT_CHUNK_SIZE; my $parts = OpenQA::File->new(file => Mojo::File->new($opts->{file}))->split($chunk_size); $self->emit('upload_chunk.prepare', $parts); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Command.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Command.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Command.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Command.pm 2026-03-30 20:36:17.000000000 +0200 @@ -13,6 +13,8 @@ use Mojo::Transaction::HTTP; use Term::ANSIColor qw(colored); +use constant MOJO_CONNECT_TIMEOUT => $ENV{MOJO_CONNECT_TIMEOUT} // 30; + my $JSON = Cpanel::JSON::XS->new->utf8->canonical->allow_nonref->allow_unknown->allow_blessed->convert_blessed ->stringify_infnan->escape_slash->allow_dupkeys->pretty; my $PARAM_RE = qr/^([[:alnum:]_\[\]\.\:]+)=(.*)$/s; @@ -118,7 +120,7 @@ } sub retry_tx ($self, $client, $tx, $retries = undef, $delay = undef) { - $client->connect_timeout($ENV{MOJO_CONNECT_TIMEOUT} // 30); + $client->connect_timeout(MOJO_CONNECT_TIMEOUT); $delay //= $ENV{OPENQA_CLI_RETRY_SLEEP_TIME_S} // 3; $retries //= $ENV{OPENQA_CLI_RETRIES} // 0; my $factor = $ENV{OPENQA_CLI_RETRY_FACTOR} // 1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/File.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/File.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/File.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/File.pm 2026-03-30 20:36:17.000000000 +0200 @@ -10,6 +10,8 @@ use Fcntl 'SEEK_SET'; use OpenQA::Files; +use constant FILE_CHUNK_SIZE => 10_000_000; + has file => sub { Mojo::File->new }; has [qw(start end index cksum total content total_cksum)]; @@ -74,7 +76,7 @@ } sub split ($self, $chunk_size = undef) { - $chunk_size //= 10000000; + $chunk_size //= FILE_CHUNK_SIZE; croak 'You need to define a file' unless defined $self->file(); $self->file(Mojo::File->new($self->file())) unless ref $self->file eq 'Mojo::File'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Markdown.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Markdown.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Markdown.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Markdown.pm 2026-03-30 20:36:17.000000000 +0200 @@ -13,11 +13,13 @@ our @EXPORT_OK = qw(bugref_to_html is_light_color markdown_to_html); +use constant IS_LIGHT_COLOR_THRESHOLD => 380; + sub is_light_color ($color) { return undef unless $color =~ m/^#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})$/; my ($red, $green, $blue) = ($1, $2, $3); my $sum = (hex $red) + (hex $green) + (hex $blue); - return $sum > 380; + return $sum > IS_LIGHT_COLOR_THRESHOLD; } sub bugref_to_html ($bugref, $fancy = 0) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Scheduler.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Scheduler.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Scheduler.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Scheduler.pm 2026-03-30 20:36:17.000000000 +0200 @@ -4,13 +4,14 @@ package OpenQA::Scheduler; use Mojo::Base 'Mojolicious', -signatures; -use OpenQA::Setup; use Mojo::IOLoop; -use OpenQA::Log qw(log_debug setup_log); use Mojo::Server::Daemon; +use Scalar::Util qw(looks_like_number); +use Time::Seconds; +use OpenQA::Log qw(log_debug setup_log); use OpenQA::Schema; use OpenQA::Scheduler::Model::Jobs; -use Scalar::Util qw(looks_like_number); +use OpenQA::Setup; # Scheduler default clock. Defaults to 20 s # Optimization rule of thumb is: @@ -92,7 +93,7 @@ $self->plugin('Gru'); # check for stale jobs every 2 minutes - Mojo::IOLoop->recurring(120 => \&_check_stale); + Mojo::IOLoop->recurring(ONE_MINUTE * 2 => \&_check_stale); # initial schedule Mojo::IOLoop->next_tick( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Schema/Result/Jobs.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Schema/Result/Jobs.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Schema/Result/Jobs.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Schema/Result/Jobs.pm 2026-03-30 20:36:17.000000000 +0200 @@ -50,6 +50,9 @@ # job settings which are defined directly as column of the jobs table use constant MAIN_SETTINGS => qw(DISTRI VERSION FLAVOR ARCH TEST MACHINE BUILD); +use constant MAX_LENGTH_REASON => 300; +use constant JOB_INVESTIGATE_GIT_TIMEOUT => 20; + __PACKAGE__->table('jobs'); __PACKAGE__->load_components(qw(InflateColumn::DateTime FilterColumn Timestamps)); __PACKAGE__->add_columns( @@ -1882,7 +1885,8 @@ sub git_diff ($self, $dir, $refspec_range, $limit = undef) { return "Invalid range $refspec_range" if $refspec_range =~ m/UNKNOWN|unreadable git hash/; - my $timeout = OpenQA::App->singleton->config->{global}->{job_investigate_git_timeout} // 20; + my $timeout = OpenQA::App->singleton->config->{global}->{job_investigate_git_timeout} + // JOB_INVESTIGATE_GIT_TIMEOUT; my $cmd = ['git', '-C', $dir, 'rev-list', '--count', $refspec_range]; my $res = run_cmd_with_log_return_error($cmd); my $out = $res->{stdout} . $res->{stderr}; @@ -2198,7 +2202,7 @@ # limit length of the reason # note: The reason can be anything the worker picked up as useful information so better cut it at a # reasonable, human-readable length. This also avoids growing the database too big. - $reason = substr($reason, 0, 300) . '…' if length $reason > 300; + $reason = substr($reason, 0, MAX_LENGTH_REASON) . '…' if length $reason > MAX_LENGTH_REASON; $reason .= $append_reason; $new_val->{reason} = $reason; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Schema/Result/ScheduledProducts.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Schema/Result/ScheduledProducts.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Schema/Result/ScheduledProducts.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Schema/Result/ScheduledProducts.pm 2026-03-30 20:36:17.000000000 +0200 @@ -10,6 +10,7 @@ use Exporter 'import'; use File::Basename; use Feature::Compat::Try; +use HTTP::Status qw(:constants); use OpenQA::App; use OpenQA::Log qw(log_debug log_warning log_error); use OpenQA::Utils; @@ -359,7 +360,7 @@ else { $result = $self->_generate_jobs($args, \@notes, $skip_chained_deps, $include_children); } - return {error => $result->{error_message}, error_code => $result->{error_code} // 400} + return {error => $result->{error_message}, error_code => $result->{error_code} // HTTP_BAD_REQUEST} if defined $result->{error_message}; my $jobs = $result->{settings_result}; # take some attributes from the first job to guess what old jobs to cancel diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Schema/Result/Workers.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Schema/Result/Workers.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Schema/Result/Workers.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Schema/Result/Workers.pm 2026-03-30 20:36:17.000000000 +0200 @@ -13,8 +13,14 @@ use OpenQA::Constants qw(WORKER_API_COMMANDS DB_TIMESTAMP_ACCURACY); use OpenQA::Jobs::Constants; use Mojo::JSON qw(encode_json decode_json); +use List::Util qw(any); +use Time::Seconds; use DBI qw(:sql_types); +use constant WS_SERVER_GRACE_PERIOD => $ENV{OPENQA_WEB_SOCKETS_GRACE_PERIOD} // (ONE_MINUTE * 5); + +use constant VNC_PORT_OFFSET => 5990; + __PACKAGE__->table('workers'); __PACKAGE__->load_components(qw(InflateColumn::DateTime Timestamps)); __PACKAGE__->add_columns( @@ -176,30 +182,34 @@ } sub send_command ($self, %args) { - return undef if (!defined $args{command}); + return undef unless defined(my $command = $args{command}); - if (!grep { $args{command} eq $_ } WORKER_API_COMMANDS) { + if (!any { $command eq $_ } WORKER_API_COMMANDS) { my $msg = 'Trying to issue unknown command "%s" for worker "%s:%n"'; - log_error(sprintf $msg, $args{command}, $self->host, $self->instance); + log_error(sprintf $msg, $command, $self->host, $self->instance); return undef; } try { - OpenQA::App->singleton->emit_event( - openqa_command_enqueue => {workerid => $self->id, command => $args{command}}); + OpenQA::App->singleton->emit_event(openqa_command_enqueue => {workerid => $self->id, command => $command}); } catch ($e) { } # prevent ws server querying itself (which would cause it to hang until the connection times out) if (OpenQA::WebSockets::Client::is_current_process_the_websocket_server) { - return OpenQA::WebSockets::ws_send($self->id, $args{command}, $args{job_id}, undef); + return OpenQA::WebSockets::ws_send($self->id, $command, $args{job_id}, undef); } my $client = OpenQA::WebSockets::Client->singleton; - try { $client->send_msg($self->id, $args{command}, $args{job_id}) } + state $first_error_time = undef; + try { $client->send_msg($self->id, $command, $args{job_id}); $first_error_time = undef; } catch ($e) { - log_error(sprintf 'Failed dispatching message to websocket server over ipc for worker "%s:%n": %s', - $self->host, $self->instance, $e); + my $msg = sprintf 'Failed to send command "%s" to websocket server (regarding worker "%s:%n"): %s', + $command, $self->host, $self->instance, $e; + my $error_time = time; + my $within_grace_period = !$first_error_time || ($error_time - $first_error_time) <= WS_SERVER_GRACE_PERIOD; + $first_error_time //= $error_time; + $within_grace_period ? log_warning($msg) : log_error($msg); return undef; } return 1; @@ -244,7 +254,7 @@ sub vnc_argument ($self) { my $hostname = $self->get_property('WORKER_HOSTNAME') || $self->host; - my $instance = $self->instance + 5990; + my $instance = $self->instance + VNC_PORT_OFFSET; return "$hostname:$instance"; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Script/CloneJob.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Script/CloneJob.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Script/CloneJob.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Script/CloneJob.pm 2026-03-30 20:36:17.000000000 +0200 @@ -168,15 +168,32 @@ sub _run_cmd ($command, @args) { system $command, @args; return $? == 0 ? '' : _format_cmd_error($command) } -sub mirror ($url_handler, $from, $dst) { - my @curl_args = @{$url_handler->{curl_args}}; - my $secrets = $url_handler->{secrets}; +sub _url_from_cmd ($command, @args) { # use open (and not qx) to avoid splitting arguments or using a shell + open my $fh, '-|', $command, @args or return "Failed to execute '$command': $!"; + my $out = do { local $/; <$fh> }; + close $fh; + return $? == 0 ? Mojo::URL->new($out) : _format_cmd_error($command); +} + +sub _args_for_header ($headers, $name) { + return (map { ('-H', "$name: $_") } @{$headers->every_header($name)}); +} + +sub _auth_args ($from, $secrets) { my $headers = Mojo::Headers->new; OpenQA::UserAgent::add_auth_headers($headers, $from, @$secrets) if $secrets; - for my $name (@{$headers->names}) { - push @curl_args, '-H', "$name: " . $_ for @{$headers->every_header($name)}; - } - _run_cmd CURL, @curl_args, qw(--continue-at - --output), $dst, $from; + return [map { _args_for_header $headers, $_ } @{$headers->names}]; +} + +sub _resolve_redirection ($from, $curl_args, $secrets) { + my @effective_url_args = qw(--silent --follow --head --output /dev/null -w %{url_effective}); + return _url_from_cmd CURL, @$curl_args, @{_auth_args($from, $secrets)}, @effective_url_args, $from; +} + +sub mirror ($url_handler, $from, $dst) { + my ($curl_args, $secrets) = ($url_handler->{curl_args}, $url_handler->{secrets}); + return $from if ref($from = _resolve_redirection $from, $curl_args, $secrets) ne 'Mojo::URL'; + return _run_cmd CURL, @$curl_args, @{_auth_args($from, $secrets)}, qw(--continue-at - --output), $dst, $from; } sub clone_job_download_assets ($jobid, $job, $url_handler, $options) { @@ -229,7 +246,7 @@ } sub make_curl_arguments ($options) { - my @args = ('--follow', '--retry', $options->{retry}, '--retry-connrefused'); + my @args = ('--retry', $options->{retry}, '--retry-connrefused'); push @args, '--no-progress-meter' unless $options->{'show-progress'}; push @args, '--verbose' if $options->{verbose}; return \@args; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Setup.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Setup.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Setup.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Setup.pm 2026-03-30 20:36:17.000000000 +0200 @@ -112,6 +112,7 @@ access_control_allow_origin_header => undef, api_hmac_time_tolerance => 300, frontpage_builds => 3, + frontpage_time_limit_days => 14, scenario_definitions_allowed_hosts => 'github.com raw.githubusercontent.com', }, rate_limits => { @@ -476,7 +477,7 @@ if (my $days = $c->app->config->{global}->{hsts}) { $c->res->headers->header( 'Strict-Transport-Security' => sprintf 'max-age=%d; includeSubDomains', - $days * 24 * 60 * 60 + $days * ONE_DAY ); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Shared/Controller/Auth.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Shared/Controller/Auth.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Shared/Controller/Auth.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Shared/Controller/Auth.pm 2026-03-30 20:36:17.000000000 +0200 @@ -8,6 +8,7 @@ use OpenQA::Log qw(log_trace); use Mojo::Util qw(hmac_sha1_sum secure_compare); use Mojo::URL; +use Time::Seconds; sub check ($self) { my $config = $self->app->config; @@ -108,7 +109,7 @@ sub _is_timestamp_valid ($self, $our_timestamp, $remote_timestamp) { my $log = $self->app->log; my $tolerance = $self->config->{api_hmac_time_tolerance} - // 300; # make extra sure this value is never empty to avoid security issues + // ONE_MINUTE * 5; # make extra sure this value is never empty to avoid security issues return 1 if (abs($our_timestamp - $remote_timestamp) <= $tolerance); $log->debug( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Shared/Controller/Running.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Shared/Controller/Running.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Shared/Controller/Running.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Shared/Controller/Running.pm 2026-03-30 20:36:17.000000000 +0200 @@ -9,16 +9,18 @@ use Mojo::JSON qw(encode_json decode_json); use Feature::Compat::Try; use File::stat; +use HTTP::Status qw(:constants); +use Time::Seconds; use OpenQA::Constants qw(WORKER_COMMAND_LIVELOG_STOP WORKER_COMMAND_LIVELOG_START); use OpenQA::Log qw(log_debug log_error); use OpenQA::Utils; use OpenQA::WebSockets::Client; use OpenQA::Jobs::Constants; use OpenQA::Schema::Result::Jobs; -use HTTP::Status qw(:constants); use constant IMAGE_STREAMING_INTERVAL => $ENV{OPENQA_IMAGE_STREAMING_INTERVAL} // 0.3; use constant TEXT_STREAMING_INTERVAL => $ENV{OPENQA_TEXT_STREAMING_INTERVAL} // 1.0; +use constant STREAMING_TIMEOUT => ONE_MINUTE * 15; sub init ($self, $page_name = undef) { my $job = $self->app->schema->resultset('Jobs')->find($self->param('testid')); @@ -82,7 +84,7 @@ my $worker = $job->worker; my $logfile = $worker->get_property('WORKER_TMPDIR') . "/$file_name"; $self->render_later; - Mojo::IOLoop->stream($self->tx->connection)->timeout(900); + Mojo::IOLoop->stream($self->tx->connection)->timeout(STREAMING_TIMEOUT); my $res = $self->res; $res->code(HTTP_OK); $res->headers->content_type('text/event-stream'); @@ -178,10 +180,10 @@ # send images via server-sent events $self->render_later; - Mojo::IOLoop->stream($self->tx->connection)->timeout(900); + Mojo::IOLoop->stream($self->tx->connection)->timeout(STREAMING_TIMEOUT); my $res = $self->res; my $headers = $res->headers; - $res->code(200); + $res->code(HTTP_OK); $headers->content_type('text/event-stream'); # set CORS headers required when not using a reverse proxy (so the port differs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Task/Job/FinalizeResults.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Task/Job/FinalizeResults.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Task/Job/FinalizeResults.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Task/Job/FinalizeResults.pm 2026-03-30 20:36:17.000000000 +0200 @@ -8,6 +8,9 @@ use Time::Seconds; use Feature::Compat::Try; +use constant DEFAULT_OPENQA_JOB_DONE_HOOK_RETRIES => 53; +use constant DEFAULT_OPENQA_JOB_DONE_HOOK_SKIP_RC => 142; + sub register ($self, $app, @args) { $app->minion->add_task(finalize_job_results => \&_finalize_results); } @@ -51,8 +54,10 @@ my $settings = $openqa_job->settings_hash; my $delay = $settings->{_TRIGGER_JOB_DONE_DELAY} // $ENV{OPENQA_JOB_DONE_HOOK_DELAY} // ONE_MINUTE; # Linear backoff with 53 retries ~ 1 day - my $retries = $settings->{_TRIGGER_JOB_DONE_RETRIES} // $ENV{OPENQA_JOB_DONE_HOOK_RETRIES} // 53; - my $skip_rc = $settings->{_TRIGGER_JOB_DONE_SKIP_RC} // $ENV{OPENQA_JOB_DONE_HOOK_SKIP_RC} // 142; + my $retries = $settings->{_TRIGGER_JOB_DONE_RETRIES} // $ENV{OPENQA_JOB_DONE_HOOK_RETRIES} + // DEFAULT_OPENQA_JOB_DONE_HOOK_RETRIES; + my $skip_rc = $settings->{_TRIGGER_JOB_DONE_SKIP_RC} // $ENV{OPENQA_JOB_DONE_HOOK_SKIP_RC} + // DEFAULT_OPENQA_JOB_DONE_HOOK_SKIP_RC; $guard->retry(0); my $id = $app->minion->enqueue( hook_script => [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/UserAgent.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/UserAgent.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/UserAgent.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/UserAgent.pm 2026-03-30 20:36:17.000000000 +0200 @@ -8,6 +8,7 @@ use Mojo::File 'path'; use Mojo::Util 'hmac_sha1_sum'; use Scalar::Util (); +use Time::Seconds; use Carp; has [qw(apikey apisecret base_url)]; @@ -25,7 +26,7 @@ # Scheduling a couple of hundred jobs takes quite some time - so we better wait a couple of minutes # (default is 20 seconds) - $self->inactivity_timeout(600); + $self->inactivity_timeout(ONE_MINUTE * 10); # Some urls might redirect to https and then there are internal redirects for assets $self->max_redirects(3); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Utils.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Utils.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Utils.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Utils.pm 2026-03-30 20:36:17.000000000 +0200 @@ -87,6 +87,7 @@ use constant ONE_SECOND_IN_MICROSECONDS => 1_000_000; use constant RANDOM_STRING_DEFAULT_LENGTH => 16; +use constant DEFAULT_OPENQA_BASE_PORT => 9526; our $VERSION = sprintf '%d.%03d', q$Revision: 1.12 $ =~ /(\d+)/g; our @EXPORT = qw( @@ -799,7 +800,7 @@ } sub service_port ($service) { - my $base = $ENV{OPENQA_BASE_PORT} ||= 9526; + my $base = $ENV{OPENQA_BASE_PORT} ||= DEFAULT_OPENQA_BASE_PORT; my $offsets = { webui => 0, websocket => 1, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebAPI/Controller/Main.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebAPI/Controller/Main.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebAPI/Controller/Main.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebAPI/Controller/Main.pm 2026-03-30 20:36:17.000000000 +0200 @@ -6,12 +6,15 @@ use Feature::Compat::Try; use Date::Format; +use Mojo::File qw(path); +use Time::Seconds; use OpenQA::Constants qw(BUILD_SORT_BY_NAME BUILD_SORT_BY_NEWEST_JOB BUILD_SORT_BY_OLDEST_JOB); use OpenQA::Jobs::Constants; use OpenQA::Schema::Result::Jobs; use OpenQA::BuildResults; use OpenQA::Utils; -use Mojo::File qw(path); + +use constant OPENQA_WEBUI_OVERVIEW_INACTIVITY_TIMEOUT => $ENV{OPENQA_WEBUI_OVERVIEW_INACTIVITY_TIMEOUT} // 90; sub dashboard_build_results ($self) { my $validation = $self->validation; @@ -23,8 +26,9 @@ $validation->optional('group'); return $self->reply->validation_error({format => $self->accepts('html', 'json')}) if $validation->has_error; - my $limit_builds = $validation->param('limit_builds') // $self->app->config->{global}->{frontpage_builds}; - my $time_limit_days = $validation->param('time_limit_days') // 14; + my $config = $self->app->config->{global}; + my $limit_builds = $validation->param('limit_builds') // $config->{frontpage_builds}; + my $time_limit_days = $validation->param('time_limit_days') // $config->{frontpage_time_limit_days}; my $only_tagged = $validation->param('only_tagged') // 0; my $default_expanded = $validation->param('default_expanded') // 0; my $show_tags = $validation->param('show_tags') // $only_tagged; @@ -109,13 +113,13 @@ return $self->reply->not_found unless my $group = $self->schema->resultset($resultset)->find($group_id); my $fullscreen = $validation->param('fullscreen') // 0; - my $interval = $validation->param('interval') // 60; + my $interval = $validation->param('interval') // ONE_MINUTE; $self->stash(fullscreen => $fullscreen, interval => $interval); my $page = $validation->param('comments_page') // 1; my $page_limit = $validation->param('comments_limit') // 5; - $self->inactivity_timeout($ENV{OPENQA_WEBUI_OVERVIEW_INACTIVITY_TIMEOUT} // 90); + $self->inactivity_timeout(OPENQA_WEBUI_OVERVIEW_INACTIVITY_TIMEOUT); # find comments my $comments = $group->comments; my $ordered_comments = $comments->search( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebAPI/Controller/Step.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebAPI/Controller/Step.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebAPI/Controller/Step.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebAPI/Controller/Step.pm 2026-03-30 20:36:17.000000000 +0200 @@ -41,6 +41,8 @@ use Mojo::JSON 'decode_json'; use Feature::Compat::Try; +use constant AVG_SIMILARITY_THRESHOLD => 70; + sub _init ($self) { return 0 unless my $job = $self->app->schema->resultset('Jobs')->find($self->param('testid')); my %attrs = (rows => 1, order_by => {-desc => 'id'}); @@ -222,7 +224,7 @@ # - tags: tags from the screenshot my $default_needle = {}; my $first_needle = $needles[0]; - if ($first_needle && ($first_needle->{avg_similarity} || 0) > 70) { + if ($first_needle && ($first_needle->{avg_similarity} || 0) > AVG_SIMILARITY_THRESHOLD) { $first_needle->{selected} = 1; $default_needle->{tags} = $first_needle->{tags}; $default_needle->{area} = $first_needle->{matches}; @@ -571,7 +573,7 @@ # note: the same needle can be shown under different tags, hence the selected flag might be occur twice # (even though we check for $has_selection here!) my $best_match = $sorted_needles[0]; - if (!$has_selection && $best_match && $best_match->{avg_similarity} > 70) { + if (!$has_selection && $best_match && $best_match->{avg_similarity} > AVG_SIMILARITY_THRESHOLD) { $has_selection = $best_match->{selected} = 1; $primary_match = $best_match; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebAPI/Plugin/ObsRsync/Task.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebAPI/Plugin/ObsRsync/Task.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebAPI/Plugin/ObsRsync/Task.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebAPI/Plugin/ObsRsync/Task.pm 2026-03-30 20:36:17.000000000 +0200 @@ -8,6 +8,9 @@ use OpenQA::Task::SignalGuard; use Feature::Compat::Try; +use constant RETRY_INTERVAL_ON_EXCEPTION => 120; +use constant RETRY_MAX_COUNT_ON_EXCEPTION => 200; + sub register ($self, $app, $conf) { $app->minion->add_task(obs_rsync_run => \&run); $app->minion->add_task(obs_rsync_update_dirty_status => \&update_dirty_status); @@ -43,9 +46,6 @@ my $helper = $app->obs_rsync; my $home = $helper->home; - my $retry_interval_on_exception = 120; - my $retry_max_count_on_exception = 200; - if ($job->info && !$job->info->{notes}{project_lock}) { return _retry_or_finish($job, $helper) unless $helper->lock($project); $job->note(project_lock => 1); @@ -53,7 +53,7 @@ my $dirty = 0; try { $dirty = $helper->is_status_dirty($project, 1) } catch ($e) { - return _retry_or_finish($job, $helper, $project, $retry_interval_on_exception, $retry_max_count_on_exception); + return _retry_or_finish($job, $helper, $project, RETRY_INTERVAL_ON_EXCEPTION, RETRY_MAX_COUNT_ON_EXCEPTION); } return _retry_or_finish($job, $helper, $project) if $dirty; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebSockets/Controller/Worker.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebSockets/Controller/Worker.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/WebSockets/Controller/Worker.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/WebSockets/Controller/Worker.pm 2026-03-30 20:36:17.000000000 +0200 @@ -16,6 +16,7 @@ use Mojo::Util 'dumper'; use constant LOG_WORKER_STATUS_MESSAGES => $ENV{OPENQA_LOG_WORKER_STATUS_MESSAGES} // 0; +use constant MAX_WEBSOCKET_SIZE => 1024 * 1024 * 10; sub ws ($self) { my $status = $self->status; @@ -29,7 +30,7 @@ $self->on(json => \&_message); $self->on(finish => \&_finish); $self->inactivity_timeout(0); # Do not force connection close due to inactivity - $self->tx->max_websocket_size(10485760); + $self->tx->max_websocket_size(MAX_WEBSOCKET_SIZE); } sub _finish ($self, $code, $reason) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker/Job.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker/Job.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker/Job.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker/Job.pm 2026-03-30 20:36:17.000000000 +0200 @@ -28,6 +28,8 @@ use Time::HiRes qw(usleep); use Feature::Compat::Try; +use constant DEFAULT_UPLOAD_CHUNK_SIZE => 1_000_000; + # define attributes for public properties has 'worker'; has 'client'; @@ -973,7 +975,7 @@ my $filename = $upload_parameter->{file}->{filename}; my $file = $upload_parameter->{file}->{file}; my $global_settings = $self->worker->settings->global_settings; - my $chunk_size = $global_settings->{UPLOAD_CHUNK_SIZE} // 1000000; + my $chunk_size = $global_settings->{UPLOAD_CHUNK_SIZE} // DEFAULT_UPLOAD_CHUNK_SIZE; my $retries = $global_settings->{UPLOAD_RETRIES} // 10; my $local_upload = $global_settings->{LOCAL_UPLOAD} // 1; my $ua = $self->client->ua; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker/Settings.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker/Settings.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker/Settings.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker/Settings.pm 2026-03-30 20:36:17.000000000 +0200 @@ -19,6 +19,8 @@ has 'webui_host_specific_settings'; use constant VNCPORT_OFFSET => $ENV{VNCPORT_OFFSET} // 90; +use constant QEMU_PORT_OFFSET => 20_002; +use constant DEFAULT_CRITICAL_LOAD_AVG_THRESHOLD => 40; sub new ($class, $instance_number = undef, $cli_options = {}) { my $config_paths = lookup_config_files(undef, 'workers.ini', 1); @@ -48,12 +50,12 @@ # Select sensible system CPU load15 threshold to prevent system overload # based on experiences with system stability so far - $global_settings{CRITICAL_LOAD_AVG_THRESHOLD} //= 40; + $global_settings{CRITICAL_LOAD_AVG_THRESHOLD} //= DEFAULT_CRITICAL_LOAD_AVG_THRESHOLD; # set some environment variables # TODO: This should be sent to the scheduler to be included in the worker's table. if (defined $instance_number) { - $ENV{QEMUPORT} = $instance_number * 10 + 20002; + $ENV{QEMUPORT} = $instance_number * 10 + QEMU_PORT_OFFSET; $ENV{VNC} = $instance_number + VNCPORT_OFFSET; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker/WebUIConnection.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker/WebUIConnection.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker/WebUIConnection.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker/WebUIConnection.pm 2026-03-30 20:36:17.000000000 +0200 @@ -15,6 +15,9 @@ # define it here for now until we can drop support for Leap 15.6 use constant _HTTP_TOO_EARLY => 425; +use constant DEFAULT_OPENQA_WORKER_CONNECT_RETRIES => 60; +use constant MAX_WEBSOCKET_SIZE => 1024 * 1024 * 10; + has 'webui_host'; # hostname:port of the web UI to connect to has 'url'; # URL of the web UI to connect to - initially deduced from webui_host (Mojo::URL instance) has 'ua'; # the OpenQA::Client used to do connections @@ -210,7 +213,7 @@ # note: The worker is supposed to handle this event and e.g. try to re-register again. }); - $tx->max_websocket_size(10485760); + $tx->max_websocket_size(MAX_WEBSOCKET_SIZE); $self->websocket_connection($tx); $self->send_status(); $self->_set_status(connected => {}); @@ -265,7 +268,8 @@ } sub configured_retries ($self) { - $ENV{OPENQA_WORKER_CONNECT_RETRIES} // $self->worker->settings->global_settings->{RETRIES} // 60; + $ENV{OPENQA_WORKER_CONNECT_RETRIES} // $self->worker->settings->global_settings->{RETRIES} + // DEFAULT_OPENQA_WORKER_CONNECT_RETRIES; } # sends a command to the web UI via its REST API diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker.pm new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker.pm --- old/openQA-5.1774854217.24dbd811/lib/OpenQA/Worker.pm 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/lib/OpenQA/Worker.pm 2026-03-30 20:36:17.000000000 +0200 @@ -51,6 +51,8 @@ use OpenQA::Worker::Job; use OpenQA::Worker::App; +use constant DEFAULT_IPMI_AUTOSHUTDOWN_INTERVAL => 300; + has 'instance_number'; has 'pool_directory'; has 'no_cleanup'; @@ -349,7 +351,7 @@ }); } - my $interval = $global_settings->{IPMI_AUTOSHUTDOWN_INTERVAL} // 300; + my $interval = $global_settings->{IPMI_AUTOSHUTDOWN_INTERVAL} // DEFAULT_IPMI_AUTOSHUTDOWN_INTERVAL; if ( $global_settings->{IPMI_HOSTNAME} && $global_settings->{IPMI_USER} && $global_settings->{IPMI_PASSWORD} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/01-compile-check-all.t new/openQA-5.1774895777.6c2911d1/t/01-compile-check-all.t --- old/openQA-5.1774854217.24dbd811/t/01-compile-check-all.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/01-compile-check-all.t 2026-03-30 20:36:17.000000000 +0200 @@ -5,19 +5,14 @@ # We need :no_end_test here because otherwise it would output a no warnings # test for each of the modules, but with the same test number use Test::Warnings qw(:no_end_test :report_warnings); +use Test::Compile; +use File::Which; use FindBin; use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib"; use OpenQA::Test::TimeLimit '400'; -use Test::Strict; -push @Test::Strict::MODULES_ENABLING_STRICT, 'Test::Most'; -push @Test::Strict::MODULES_ENABLING_WARNINGS, 'Test::Most'; - -$Test::Strict::TEST_SYNTAX = 1; -$Test::Strict::TEST_STRICT = 1; -$Test::Strict::TEST_WARNINGS = 1; -$Test::Strict::TEST_SKIP = [ +my $SKIP = [ # skip test module which would require test API from os-autoinst to be present 't/data/openqa/share/tests/opensuse/tests/installation/installer_timezone.pm', # Skip data files which are supposed to resemble generated output which has no 'use' statements @@ -28,4 +23,34 @@ 't/data/openqa-trigger-from-obs/Proj2::appliances/.dirty_status', 't/data/openqa-trigger-from-obs/Proj3::standard/empty.txt', ]; -all_perl_files_ok(qw(lib script t)); + +my $test = Test::Compile->new(); +my @files; + +# Prevent any non-tracked files or files within .git (e.g. in.git/rr-cache) to +# interfer +if (-d '.git' and which('git')) { + my $root = qx{git rev-parse --show-toplevel}; + chomp $root; + $root .= '/'; + my @all_git_files = qx{git ls-files}; + chomp @all_git_files; + my %skip = map { $_ => undef } @$SKIP; + @files = map { $root . $_ } + grep { !-l $_ && !exists $skip{$_} && $_ !~ /^(external|t)\// } @all_git_files; # Exclude files to skip +} +else { + @files = ($test->all_pm_files('lib'), $test->all_pl_files('script')); + my %skip = map { $_ => undef } @$SKIP; + @files = grep { my $f = s{^\./}{}r; !exists $skip{$f} && $f !~ /^t\// } @files; +} + +# Only check perl files and skip test scripts (already executed) +@files = grep { /\.(?:pm|pl|t)$/ } @files; + +plan tests => scalar @files; + +foreach my $file (@files) { + my $ok = $file =~ /\.pm$/ ? $test->pm_file_compiles($file) : $test->pl_file_compiles($file); + ok $ok, "Syntax check $file"; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/11-commands.t new/openQA-5.1774895777.6c2911d1/t/11-commands.t --- old/openQA-5.1774854217.24dbd811/t/11-commands.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/11-commands.t 2026-03-30 20:36:17.000000000 +0200 @@ -3,24 +3,28 @@ # SPDX-License-Identifier: GPL-2.0-or-later use Test::Most; +use Mojo::Base -signatures; use FindBin; use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib"; use Test::Warnings qw(:all :report_warnings); use Test::Output 'stderr_like'; +use Test::Mock::Time; use OpenQA::Test::Case; use OpenQA::Test::TimeLimit '10'; +use OpenQA::Schema::Result::Workers; use OpenQA::WebSockets::Client; +use OpenQA::Constants qw(WORKER_COMMAND_QUIT); use Test::MockModule; use Mojolicious; use Mojo::Message; my $mock_client = Test::MockModule->new('OpenQA::WebSockets::Client'); -my ($client_called, $last_command); +my ($client_called, $last_command, $fake_error); $mock_client->redefine( - send_msg => sub { - my ($self, $workerid, $command, $jobid) = @_; + send_msg => sub ($self, $workerid, $command, $jobid) { + die $fake_error if $fake_error; $client_called++; $last_command = $command; }); @@ -49,6 +53,18 @@ isnt $last_command, 'foo', 'refuse invalid commands'; ok $client_called, 'mocked send_msg method has been called'; +subtest 'dispatch fails' => sub { + my $grace_period = OpenQA::Schema::Result::Workers::WS_SERVER_GRACE_PERIOD; + my $expected_msg = qr/Failed.*command.*quit.*to websocket.*regarding.*localhost.*foo/; + my $send_cmd = sub () { $worker->send_command(command => WORKER_COMMAND_QUIT) }; + $fake_error = 'foo'; + stderr_like { $send_cmd->() } qr/\[WARN\] $expected_msg/, 'just a warning'; + sleep $grace_period; + stderr_like { $send_cmd->() } qr/\[WARN\] $expected_msg/, 'still a warning within grace period'; + sleep 1; + stderr_like { $send_cmd->() } qr/\[ERROR\] $expected_msg/, 'error after grace period exceeded'; +}; + subtest 'ws server does not try to query itself' => sub { OpenQA::WebSockets::Client::mark_current_process_as_websocket_server; $last_command = undef; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/17-build_tagging.t new/openQA-5.1774895777.6c2911d1/t/17-build_tagging.t --- old/openQA-5.1774854217.24dbd811/t/17-build_tagging.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/17-build_tagging.t 2026-03-30 20:36:17.000000000 +0200 @@ -141,7 +141,7 @@ $t->get_ok('/dashboard_build_results?show_tags=' . $enabled)->status_is(200); is scalar @{$t->tx->res->dom->find('a[href^=/tests/]')}, 9, "all builds shown on index page (show_tags=$enabled)"; - is scalar @{$t->tx->res->dom->find("i[title='important']")}, + is scalar @{$t->tx->res->dom->find("span[title='important']")}, $enabled, "tag (not) shown on index page (show_tags=$enabled)"; } }; @@ -336,22 +336,22 @@ post_comment_1001('tag:5000:important:fallback'); $t->get_ok('/group_overview/1001')->status_is(200); - $t->text_is('#tag-1001-1_2_2-5000 i', 'fallback', 'version 1.2-2 has fallback tag'); - $t->text_is('#tag-1001-1_2_1-5000 i', 'fallback', 'version 1.2-1 has fallback tag'); + $t->text_is('#tag-1001-1_2_2-5000 span.tag', 'fallback', 'version 1.2-2 has fallback tag'); + $t->text_is('#tag-1001-1_2_1-5000 span.tag', 'fallback', 'version 1.2-1 has fallback tag'); post_comment_1001('tag:1.2-2-5000:important:second'); $t->get_ok('/group_overview/1001')->status_is(200); - $t->text_is('#tag-1001-1_2_2-5000 i', 'second', 'version 1.2-2 has version-specific tag'); - $t->text_is('#tag-1001-1_2_1-5000 i', 'fallback', 'version 1.2-1 has still fallback tag'); + $t->text_is('#tag-1001-1_2_2-5000 span.tag', 'second', 'version 1.2-2 has version-specific tag'); + $t->text_is('#tag-1001-1_2_1-5000 span.tag', 'fallback', 'version 1.2-1 has still fallback tag'); post_comment_1001('tag:1.2-1-5000:important:first'); $t->get_ok('/group_overview/1001')->status_is(200); - $t->text_is('#tag-1001-1_2_2-5000 i', 'second', 'version 1.2-2 has version-specific tag'); - $t->text_is('#tag-1001-1_2_1-5000 i', 'first', 'version 1.2-1 has version-specific tag'); + $t->text_is('#tag-1001-1_2_2-5000 span.tag', 'second', 'version 1.2-2 has version-specific tag'); + $t->text_is('#tag-1001-1_2_1-5000 span.tag', 'first', 'version 1.2-1 has version-specific tag'); post_comment_1001('tag:1.2-1-5000:important:label-with.specialchars'); $t->get_ok('/group_overview/1001')->status_is(200); - $t->text_is('#tag-1001-1_2_1-5000 i', 'label-with.specialchars', 'version 1.2-1 has version-specific tag'); + $t->text_is('#tag-1001-1_2_1-5000 span.tag', 'label-with.specialchars', 'version 1.2-1 has version-specific tag'); }; subtest 'content negotiation' => sub { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/22-dashboard.t new/openQA-5.1774895777.6c2911d1/t/22-dashboard.t --- old/openQA-5.1774854217.24dbd811/t/22-dashboard.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/22-dashboard.t 2026-03-30 20:36:17.000000000 +0200 @@ -225,7 +225,7 @@ 'link URLs'; $t->element_count_is("div.children-$default_expanded .badge-all-passed", 1, 'badge shown on parent-level'); - $t->element_count_is("div.children-$default_expanded .h4 span i.tag", 0, 'no tags shown yet'); + $t->element_count_is("div.children-$default_expanded .h4 span span.tag", 0, 'no tags shown yet'); } check_test_parent('collapsed'); @@ -250,15 +250,15 @@ sub check_tags { $t->get_ok('/dashboard_build_results?limit_builds=20&show_tags=1')->status_is(200); - my @tags = $t->tx->res->dom->find('div.children-collapsed span i.tag')->map('text')->each; + my @tags = $t->tx->res->dom->find('div.children-collapsed span span.tag')->map('text')->each; is_deeply \@tags, ['some_tag'], 'tag is shown on parent-level'; $t->get_ok('/parent_group_overview/' . $test_parent->id . '?limit_builds=20&show_tags=1')->status_is(200); - @tags = $t->tx->res->dom->find('div.children-expanded span i.tag')->map('text')->each; + @tags = $t->tx->res->dom->find('div.children-expanded span span.tag')->map('text')->each; is_deeply \@tags, ['some_tag'], 'tag is shown on parent-level'; $t->get_ok('/dashboard_build_results?limit_builds=20&only_tagged=1')->status_is(200); - @tags = $t->tx->res->dom->find('div.children-collapsed span i.tag')->map('text')->each; + @tags = $t->tx->res->dom->find('div.children-collapsed span span.tag')->map('text')->each; is_deeply \@tags, ['some_tag'], 'tag is shown on parent-level (only tagged)'; @h4 = $t->tx->res->dom->find('div.children-collapsed .h4 a')->map('text')->each; is_deeply \@h4, ['Build0092'], 'only tagged builds on parent-level shown'; @@ -272,7 +272,7 @@ # use version-build format where version doesn't matches $tag_for_0092_comment->update({text => 'tag:5-0092:important:some_tag', user_id => 99901}); $t->get_ok('/dashboard_build_results?limit_builds=20&only_tagged=1')->status_is(200); -my @tags = $t->tx->res->dom->find('div.children-collapsed .h4 span i.tag')->map('text')->each; +my @tags = $t->tx->res->dom->find('div.children-collapsed .h4 span span.tag')->map('text')->each; is_deeply \@tags, [], 'tag is not shown on parent-level because version does not match'; @h4 = $t->tx->res->dom->find('div.children-collapsed .h4 a')->map('text')->each; is_deeply \@h4, [], 'also no tagged builds on parent-level shown'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/25-cache-service.t new/openQA-5.1774895777.6c2911d1/t/25-cache-service.t --- old/openQA-5.1774854217.24dbd811/t/25-cache-service.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/25-cache-service.t 2026-03-30 20:36:17.000000000 +0200 @@ -132,7 +132,7 @@ qr/rsync error:.*/, 'rsync error message'; my $status2 = $cache_client->status($rsync_request2); - is $status2->result, 'exit code 11', "exit code ok, run $run"; + is $status2->result, 'exit code 23', "exit code as expected, run $run"; ok $status2->output, "output ok, run $run"; like $status2->output, qr/Calling: rsync .* --timeout 1800 .*/s, "output correct, run $run" @@ -663,6 +663,18 @@ subtest 'OpenQA::CacheService::Task::Sync' => sub { test_sync $_ for (1 .. 4); + + subtest 'Sync to non-existent parent' => sub { + my $from = tempdir; + $from->child('testfile')->spew('some data'); + my $to = tempdir->child('non_existent_parent')->child('target_host'); + my $rsync_request = $cache_client->rsync_request(from => $from, to => $to); + ok !$cache_client->enqueue($rsync_request); + perform_minion_jobs($t->app->minion); + wait_for_or_bail_out { $cache_client->status($rsync_request)->is_processed } 'rsync'; + is $cache_client->status($rsync_request)->result, 'exit code 0', 'Sync successful with missing parent'; + ok -e $to->child('tests')->child('testfile'), 'File synced correctly'; + }; }; done_testing; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/35-script_clone_job.t new/openQA-5.1774895777.6c2911d1/t/35-script_clone_job.t --- old/openQA-5.1774854217.24dbd811/t/35-script_clone_job.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/35-script_clone_job.t 2026-03-30 20:36:17.000000000 +0200 @@ -58,6 +58,19 @@ qr/can't exec/i, 'exec error logged'; like $run->('false'), qr/false.*exited.*1/i, 'error if command fails'; is $run->('true'), '', 'no error if command succeeds'; + + subtest 'get URL from command' => sub { + my $url_from_cmd = \&OpenQA::Script::CloneJob::_url_from_cmd; + my $res = $url_from_cmd->(echo => qw(-n http://foo/bar)); + is $res, 'http://foo/bar', 'got URL'; + is ref $res, 'Mojo::URL', 'URL returned as Mojo::URL ref'; + combined_like { $res = $url_from_cmd->('does-not-exist') } qr/can't exec/i, 'exec error logged'; + like $res, qr/Failed.*does-not-exist.*No such/i, 'got error via _url_from_cmd'; + is ref $res, '', 'error returned as scalar'; + $res = $url_from_cmd->('false'); + like $res, qr/false.*exited.*status 1/i, 'got error via _url_from_cmd for non-zero return exit status'; + is ref $res, '', 'error for non-zero exit status returned as scalar'; + }; }; subtest 'getting job' => sub { @@ -493,25 +506,25 @@ subtest 'invoking curl passing auth headers' => sub { my $clone_mock = Test::MockModule->new('OpenQA::Script::CloneJob'); my @invoked_cmds; - $clone_mock->redefine( - _run_cmd => sub (@args) { - push @invoked_cmds, map { m/(.*hash:|.*time:)/i ? "$1 ?" : "$_" } @args; - ''; - }); + my $record_cmd = sub (@args) { + push @invoked_cmds, [map { m/(.*hash:|.*time:)/i ? "$1 ?" : "$_" } @args]; + }; + $clone_mock->redefine(_run_cmd => sub (@args) { $record_cmd->(@args); '' }); + $clone_mock->redefine(_url_from_cmd => sub (@args) { $record_cmd->(@args); Mojo::URL->new('redirected-url') }); note 'config path: ' . ($ENV{OPENQA_CONFIG} = "$FindBin::Bin/data"); my $args = OpenQA::Script::CloneJob::make_curl_arguments({retry => 5}); - my @expected_default_args = qw(--follow --retry 5 --retry-connrefused --no-progress-meter); + my @expected_default_args = qw(--retry 5 --retry-connrefused --no-progress-meter); is_deeply $args, \@expected_default_args, 'default arguments correct'; my $secrets = OpenQA::Script::CloneJob::read_secrets('testapi'); is_deeply $secrets, [qw(PERCIVALKEY02 PERCIVALSECRET02)], 'key and secret as expected for host "testapi"'; my %url_handler = (curl_args => $args, secrets => $secrets); my $error = OpenQA::Script::CloneJob::mirror(\%url_handler, Mojo::URL->new('url'), 'path'); + push @expected_default_args, map { -H => $_ } ('X-API-Hash: ?', 'X-API-Key: PERCIVALKEY02', 'X-API-Microtime: ?'); my @expected_cmds = ( - curl => @expected_default_args, - (map { -H => $_ } ('X-API-Hash: ?', 'X-API-Key: PERCIVALKEY02', 'X-API-Microtime: ?')), - qw(--continue-at - --output path url), + [curl => @expected_default_args, qw(--silent --follow --head --output /dev/null -w %{url_effective} url)], + [curl => @expected_default_args, qw(--continue-at - --output path redirected-url)], ); is $error, '', 'no error returned'; is_deeply \@invoked_cmds, \@expected_cmds, 'invoked expected commands' or always_explain \@invoked_cmds; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/api/04-jobs.t new/openQA-5.1774895777.6c2911d1/t/api/04-jobs.t --- old/openQA-5.1774854217.24dbd811/t/api/04-jobs.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/api/04-jobs.t 2026-03-30 20:36:17.000000000 +0200 @@ -884,7 +884,7 @@ is_deeply find_most_recent_event($schema, 'job_cancel'), {id => 99963, reason => undef}, 'cancellation was logged'; $jobs->search({id => 99963})->update({assigned_worker_id => 1, result => NONE}); - combined_like { $t->post_ok('/api/v1/jobs/99963/cancel?reason=Undecided') } qr/Failed dispatching message/s, + combined_like { $t->post_ok('/api/v1/jobs/99963/cancel?reason=Undecided') } qr/Failed to send command "cancel"/s, 'tried to send cancellation to worker'; $t->status_is(200, 'cancellation considered successful even if sending command to worker failed'); is_deeply find_most_recent_event($schema, 'job_cancel'), {id => 99963, reason => 'Undecided'}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/t/ui/18-tests-details.t new/openQA-5.1774895777.6c2911d1/t/ui/18-tests-details.t --- old/openQA-5.1774854217.24dbd811/t/ui/18-tests-details.t 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/t/ui/18-tests-details.t 2026-03-30 20:36:17.000000000 +0200 @@ -496,14 +496,40 @@ 'video src correct and starts on timestamp'; }; +subtest 'arrow key navigation between steps' => sub { + $driver->get('/tests/99946'); + wait_for_ajax(msg => 'test 99946 loaded'); + $driver->find_element_by_link_text('Details')->click(); + wait_for_ajax(msg => 'details tab loaded'); + + $driver->find_element('[href="#step/bootloader/1"]')->click(); + wait_for_ajax(msg => 'first step of bootloader loaded'); + my $first_step_url = $driver->get_current_url(); + like $first_step_url, qr/#step\/bootloader\/1/, 'first step selected'; + + $driver->execute_script("\$(window).trigger(\$.Event('keydown', {key: 'ArrowRight'}))"); + wait_for_ajax(msg => 'arrow right pressed via jQuery'); + my $second_step_url = $driver->get_current_url(); + like $second_step_url, qr/#step\/bootloader\/2/, 'second step selected after right arrow'; + + $driver->execute_script("\$(window).trigger(\$.Event('keydown', {key: 'ArrowLeft'}))"); + wait_for_ajax(msg => 'arrow left pressed via jQuery'); + my $back_to_first_url = $driver->get_current_url(); + like $back_to_first_url, qr/#step\/bootloader\/1/, 'back to first step after left arrow'; + + $driver->execute_script("\$(window).trigger(\$.Event('keydown', {key: 'Escape'}))"); + wait_for_ajax(msg => 'escape pressed to close preview'); +}; + subtest 'misc details: title, favicon, go back, go to source view, go to log view' => sub { - $driver->go_back(); # to 99946 + $driver->get('/tests/99946'); + wait_for_ajax(msg => 'test 99946 loaded'); $driver->title_is('openQA: opensuse-13.1-DVD-i586-Build0091-textmode@32bit test results', 'tests/99946 followed'); like $driver->find_element('link[rel=icon]')->get_attribute('href'), qr/logo-passed/, 'favicon is based on job result'; wait_for_ajax(msg => 'test details tab for job 99946 loaded (1)'); - if (ok my $current_preview = $driver->find_element('.current_preview'), 'state preserved when going back') { - $current_preview->click; + if (my @previews = $driver->find_elements('.current_preview')) { + $previews[0]->click; # uncoverable statement } $driver->find_element_by_link_text('installer_timezone')->click(); like diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/templates/webapi/main/group_builds.html.ep new/openQA-5.1774895777.6c2911d1/templates/webapi/main/group_builds.html.ep --- old/openQA-5.1774854217.24dbd811/templates/webapi/main/group_builds.html.ep 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/templates/webapi/main/group_builds.html.ep 2026-03-30 20:36:17.000000000 +0200 @@ -29,7 +29,7 @@ %= include 'main/review_badge', group_build_id => $group_build_id, build_res => $build_res, id_prefix => '' % if (my $tag = $build_res->{tag}) { <span id="tag-<%= $group_build_id %>"> - <i class="tag fa-solid fa-tag" title="<%= $tag->{type}; %>"><%= $tag->{description} %></i> + <span class="tag" title="<%= $tag->{type}; %>"><i class="fa-solid fa-tag"></i><%= $tag->{description} %></span> </span> % } </span> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/templates/webapi/main/group_builds_functionality_view.html.ep new/openQA-5.1774895777.6c2911d1/templates/webapi/main/group_builds_functionality_view.html.ep --- old/openQA-5.1774854217.24dbd811/templates/webapi/main/group_builds_functionality_view.html.ep 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/templates/webapi/main/group_builds_functionality_view.html.ep 2026-03-30 20:36:17.000000000 +0200 @@ -31,7 +31,7 @@ % if (my $tag = $build_res->{tag}) { <span id="tag-byGroup-<%= $group_build_id %>"> - <i class="tag-byGroup fa-solid fa-tag" title="<%= $tag->{type}; %>"><%= $tag->{description} %></i> + <span class="tag-byGroup" title="<%= $tag->{type}; %>"><i class="fa-solid fa-tag"></i><%= $tag->{description} %></span> </span> % } </div> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/templates/webapi/main/index.html.ep new/openQA-5.1774895777.6c2911d1/templates/webapi/main/index.html.ep --- old/openQA-5.1774854217.24dbd811/templates/webapi/main/index.html.ep 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/templates/webapi/main/index.html.ep 2026-03-30 20:36:17.000000000 +0200 @@ -56,11 +56,11 @@ <strong style="display: block;">Limit builds</strong> <p class="d-flex flex-row align-items-center p-1"> <label class="form-label">Maximum number of builds per group</label> - <input type="number" class="form-control" name="limit_builds" value="3" id="filter-limit-builds"> + <input type="number" class="form-control" name="limit_builds" value="<%= app->config->{global}->{frontpage_builds} %>" id="filter-limit-builds"> </p> <p class="d-flex flex-row align-items-center p-1"> <label class="form-label">Maximum age in days</label> - <input type="number" class="form-control" name="time_limit_days" value="14" id="filter-time-limit-days"> + <input type="number" class="form-control" name="time_limit_days" value="<%= app->config->{global}->{frontpage_time_limit_days} %>" id="filter-time-limit-days"> </p> </div> <div class="mb-3"> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1774854217.24dbd811/tools/ci/ci-packages.txt new/openQA-5.1774895777.6c2911d1/tools/ci/ci-packages.txt --- old/openQA-5.1774854217.24dbd811/tools/ci/ci-packages.txt 2026-03-30 09:03:37.000000000 +0200 +++ new/openQA-5.1774895777.6c2911d1/tools/ci/ci-packages.txt 2026-03-30 20:36:17.000000000 +0200 @@ -229,6 +229,7 @@ perl-TAP-Harness-JUnit-0.42 perl-Task-Weaken-1.06 perl-Test-CheckGitStatus-0.1.2 +perl-Test-Compile perl-Test-Deep-1.205.0 perl-Test-Differences-0.710.0 perl-Test-Exception-0.430000 @@ -241,7 +242,6 @@ perl-Test-Output-1.034 perl-Test-Perl-Critic-1.40.0 perl-Test-Pod-1.52 -perl-Test-Strict-0.52 perl-Test-Warn-0.37 perl-Test-Warnings-0.38.0 perl-Text-Brew-0.02 ++++++ openQA.obsinfo ++++++ --- /var/tmp/diff_new_pack.FLRthc/_old 2026-03-31 15:29:24.542074092 +0200 +++ /var/tmp/diff_new_pack.FLRthc/_new 2026-03-31 15:29:24.554074592 +0200 @@ -1,5 +1,5 @@ name: openQA -version: 5.1774854217.24dbd811 -mtime: 1774854217 -commit: 24dbd8114efeea41ee484b56c5b3c633d6b5ac97 +version: 5.1774895777.6c2911d1 +mtime: 1774895777 +commit: 6c2911d1c93911b8621226c14ceb6332e0e2dd45
