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-05-04 12:51:19 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openQA (Old) and /work/SRC/openSUSE:Factory/.openQA.new.30200 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openQA" Mon May 4 12:51:19 2026 rev:842 rq:1350373 version:5.1777617029.fd9e1e69 Changes: -------- --- /work/SRC/openSUSE:Factory/openQA/openQA.changes 2026-04-30 20:32:04.918931431 +0200 +++ /work/SRC/openSUSE:Factory/.openQA.new.30200/openQA.changes 2026-05-04 12:53:59.818664554 +0200 @@ -1,0 +2,16 @@ +Fri May 01 06:30:37 UTC 2026 - [email protected] + +- Update to version 5.1777617029.fd9e1e69: + * style: Enforce perlcritic policy ProhibitSingleCharAlternation + * fix(apparmor): Add jsonnet to worker profile + * style: Enforce perlcritic policy CodeLayout::ProhibitQuotedWordLists + * style: Enforce perlcritic policy RequireNumberSeparators + * build(deps-dev): bump eslint from 10.1.0 to 10.2.1 + * style: Enforce perlcritic policy Variables::ProhibitUnusedVariables + * feat: Ensure temporary directory exists when caching assets + * fix(docs): fix mobile view rendering by ensuring CSS override + * chore(lint): extend stylelint to cover documentation CSS + * refactor: break down create_from_settings into smaller functions + * feat: automatically add configurable worker classes to jobs + +------------------------------------------------------------------- Old: ---- openQA-5.1777474261.55e5cd5e.obscpio New: ---- openQA-5.1777617029.fd9e1e69.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openQA-client-test.spec ++++++ --- /var/tmp/diff_new_pack.tZPAXP/_old 2026-05-04 12:54:04.174843843 +0200 +++ /var/tmp/diff_new_pack.tZPAXP/_new 2026-05-04 12:54:04.182844172 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-client Name: %{short_name}-test -Version: 5.1777474261.55e5cd5e +Version: 5.1777617029.fd9e1e69 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-devel-test.spec ++++++ --- /var/tmp/diff_new_pack.tZPAXP/_old 2026-05-04 12:54:04.482856519 +0200 +++ /var/tmp/diff_new_pack.tZPAXP/_new 2026-05-04 12:54:04.498857178 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-devel Name: %{short_name}-test -Version: 5.1777474261.55e5cd5e +Version: 5.1777617029.fd9e1e69 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-test.spec ++++++ --- /var/tmp/diff_new_pack.tZPAXP/_old 2026-05-04 12:54:04.774868538 +0200 +++ /var/tmp/diff_new_pack.tZPAXP/_new 2026-05-04 12:54:04.798869526 +0200 @@ -18,7 +18,7 @@ %define short_name openQA Name: %{short_name}-test -Version: 5.1777474261.55e5cd5e +Version: 5.1777617029.fd9e1e69 Release: 0 Summary: Test package for openQA License: GPL-2.0-or-later ++++++ openQA-worker-test.spec ++++++ --- /var/tmp/diff_new_pack.tZPAXP/_old 2026-05-04 12:54:05.078881051 +0200 +++ /var/tmp/diff_new_pack.tZPAXP/_new 2026-05-04 12:54:05.090881545 +0200 @@ -18,7 +18,7 @@ %define short_name openQA-worker Name: %{short_name}-test -Version: 5.1777474261.55e5cd5e +Version: 5.1777617029.fd9e1e69 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA.spec ++++++ --- /var/tmp/diff_new_pack.tZPAXP/_old 2026-05-04 12:54:05.482897679 +0200 +++ /var/tmp/diff_new_pack.tZPAXP/_new 2026-05-04 12:54:05.506898667 +0200 @@ -99,7 +99,7 @@ %define devel_requires %devel_no_selenium_requires chromedriver Name: openQA -Version: 5.1777474261.55e5cd5e +Version: 5.1777617029.fd9e1e69 Release: 0 Summary: The openQA web-frontend, scheduler and tools License: GPL-2.0-or-later ++++++ node_modules.obscpio ++++++ Binary files old/@eslint-config-array-0.23.3.tgz and new/@eslint-config-array-0.23.3.tgz differ Binary files old/@eslint-config-array-0.23.5.tgz and new/@eslint-config-array-0.23.5.tgz differ Binary files old/@eslint-config-helpers-0.5.3.tgz and new/@eslint-config-helpers-0.5.3.tgz differ Binary files old/@eslint-config-helpers-0.5.5.tgz and new/@eslint-config-helpers-0.5.5.tgz differ Binary files old/@eslint-core-1.1.1.tgz and new/@eslint-core-1.1.1.tgz differ Binary files old/@eslint-core-1.2.1.tgz and new/@eslint-core-1.2.1.tgz differ Binary files old/@eslint-object-schema-3.0.3.tgz and new/@eslint-object-schema-3.0.3.tgz differ Binary files old/@eslint-object-schema-3.0.5.tgz and new/@eslint-object-schema-3.0.5.tgz differ Binary files old/@eslint-plugin-kit-0.6.1.tgz and new/@eslint-plugin-kit-0.6.1.tgz differ Binary files old/@eslint-plugin-kit-0.7.1.tgz and new/@eslint-plugin-kit-0.7.1.tgz differ Binary files old/eslint-10.1.0.tgz and new/eslint-10.1.0.tgz differ Binary files old/eslint-10.2.1.tgz and new/eslint-10.2.1.tgz differ ++++++ node_modules.spec.inc ++++++ --- /var/tmp/diff_new_pack.tZPAXP/_old 2026-05-04 12:54:08.251011607 +0200 +++ /var/tmp/diff_new_pack.tZPAXP/_new 2026-05-04 12:54:08.275012594 +0200 @@ -11,12 +11,12 @@ Source1010: https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.0.tgz#/@csstools-selector-specificity-6.0.0.tgz Source1011: https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#/@eslint-community-eslint-utils-4.9.1.tgz Source1012: https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#/@eslint-community-regexpp-4.12.2.tgz -Source1013: https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz#/@eslint-config-array-0.23.3.tgz -Source1014: https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz#/@eslint-config-helpers-0.5.3.tgz -Source1015: https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz#/@eslint-core-1.1.1.tgz +Source1013: https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz#/@eslint-config-array-0.23.5.tgz +Source1014: https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz#/@eslint-config-helpers-0.5.5.tgz +Source1015: https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz#/@eslint-core-1.2.1.tgz Source1016: https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz#/@eslint-js-10.0.1.tgz -Source1017: https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz#/@eslint-object-schema-3.0.3.tgz -Source1018: https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz#/@eslint-plugin-kit-0.6.1.tgz +Source1017: https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz#/@eslint-object-schema-3.0.5.tgz +Source1018: https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz#/@eslint-plugin-kit-0.7.1.tgz Source1019: https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz#/@fortawesome-fontawesome-free-6.7.2.tgz Source1020: https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz#/@humanfs-core-0.19.1.tgz Source1021: https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz#/@humanfs-node-0.16.7.tgz @@ -103,7 +103,7 @@ Source1102: https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz#/env-paths-2.2.1.tgz Source1103: https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz#/error-ex-1.3.4.tgz Source1104: https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#/escape-string-regexp-4.0.0.tgz -Source1105: https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz#/eslint-10.1.0.tgz +Source1105: https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz#/eslint-10.2.1.tgz Source1106: https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz#/eslint-config-prettier-10.1.8.tgz Source1107: https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz#/eslint-plugin-prettier-5.5.5.tgz Source1108: https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz#/eslint-scope-9.1.2.tgz ++++++ openQA-5.1777474261.55e5cd5e.obscpio -> openQA-5.1777617029.fd9e1e69.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/.perlcriticrc new/openQA-5.1777617029.fd9e1e69/.perlcriticrc --- old/openQA-5.1777474261.55e5cd5e/.perlcriticrc 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/.perlcriticrc 2026-05-01 08:30:29.000000000 +0200 @@ -1,6 +1,6 @@ theme = community + openqa severity = 4 -include = strict ValuesAndExpressions::ProhibitInterpolationOfLiterals CodeLayout::ProhibitParensWithBuiltins BuiltinFunctions::RequireBlockMap BuiltinFunctions::ProhibitStringySplit ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions ValuesAndExpressions::RequireQuotedHeredocTerminator +include = strict ValuesAndExpressions::ProhibitInterpolationOfLiterals CodeLayout::ProhibitParensWithBuiltins BuiltinFunctions::RequireBlockMap BuiltinFunctions::ProhibitStringySplit ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions ValuesAndExpressions::RequireQuotedHeredocTerminator Variables::ProhibitUnusedVariables ValuesAndExpressions::RequireNumberSeparators CodeLayout::ProhibitQuotedWordLists RegularExpressions::ProhibitSingleCharAlternation # ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions # No operators like < =~ ! allowed in 'unless' or 'until', only simple @@ -46,3 +46,7 @@ # -- Superfluous use strict/warning. [OpenQA::RedundantStrictWarning] equivalent_modules = Test::Most + +[ValuesAndExpressions::RequireNumberSeparators] +min_value = 1000000 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/docs/style.css new/openQA-5.1777617029.fd9e1e69/docs/style.css --- old/openQA-5.1777474261.55e5cd5e/docs/style.css 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/docs/style.css 2026-05-01 08:30:29.000000000 +0200 @@ -1,5 +1,4 @@ /* openQA Documentation Style (Mimicking Asciidoctor) */ - body { font-family: 'Open Sans', 'DejaVu Sans', sans-serif; color: #333; @@ -7,9 +6,7 @@ margin: 0; display: flex; overflow-wrap: break-word; - word-wrap: break-word; } - /* Sidebar TOC */ #TOC { position: fixed; @@ -23,49 +20,24 @@ font-size: 0.9em; z-index: 1000; } - /* Add dots after section numbers */ -.toc-section-number::after, -.header-section-number::after { +.toc-section-number::after, .header-section-number::after { content: '. '; } - -/* Responsive design */ -@media (max-width: 800px) { - body { - flex-direction: column; - } - #TOC { - position: static; - width: 100%; - height: auto; - border-right: none; - border-bottom: 1px solid #ddd; - } - main { - margin-left: 0; - padding: 20px; - } -} - #TOC ul { list-style: none; padding-left: 20px; } - #TOC > ul { padding-left: 0; } - #TOC a { text-decoration: none; color: #0056b3; } - #TOC a:hover { text-decoration: underline; } - /* Main Content */ main { margin-left: 300px; @@ -74,19 +46,12 @@ flex-grow: 1; min-width: 0; } - -h1, -h2, -h3, -h4, -h5, -h6 { +h1, h2, h3, h4, h5, h6 { color: #ba3925; font-weight: 300; margin-top: 1.5em; margin-bottom: 0.5em; } - h1 { font-size: 2.2em; border-bottom: 1px solid #eee; @@ -100,7 +65,6 @@ h3 { font-size: 1.4em; } - /* Section Anchors */ .anchor { margin-left: 10px; @@ -109,16 +73,9 @@ font-size: 0.8em; text-decoration: none; } - -h1:hover .anchor, -h2:hover .anchor, -h3:hover .anchor, -h4:hover .anchor, -h5:hover .anchor, -h6:hover .anchor { +h1:hover .anchor, h2:hover .anchor, h3:hover .anchor, h4:hover .anchor, h5:hover .anchor, h6:hover .anchor { visibility: visible; } - /* Code blocks */ pre { background: #f4f4f4; @@ -127,7 +84,6 @@ border-radius: 4px; overflow-x: auto; } - code { font-family: 'Droid Sans Mono', 'Courier New', monospace; background: #fdf6f2; @@ -135,13 +91,11 @@ border-radius: 3px; color: #ba3925; } - pre code { background: transparent; padding: 0; color: inherit; } - /* Admonitions */ blockquote { margin: 1.5em 0; @@ -149,18 +103,12 @@ border-left: 5px solid #ccc; background: #f9f9f9; } - /* Specific styling for Note/Warning/Tip */ -p > strong:first-child:contains('Note:'), -p > strong:first-child:contains('Warning:'), -p > strong:first-child:contains('Tip:'), -p > strong:first-child:contains('Important:'), -p > strong:first-child:contains('Caution:') { +p > strong:first-child:contains('Note:'), p > strong:first-child:contains('Warning:'), p > strong:first-child:contains('Tip:'), p > strong:first-child:contains('Important:'), p > strong:first-child:contains('Caution:') { display: block; margin-bottom: 5px; text-transform: uppercase; } - /* We use a different approach since :contains is not standard CSS */ .admonition-note { border-left-color: #1f70c2; @@ -174,7 +122,6 @@ border-left-color: #28a745; background: #f6fff6; } - /* Images */ img { max-width: 100%; @@ -182,7 +129,6 @@ display: block; margin: 1em 0; } - footer { margin-top: 50px; font-size: 0.8em; @@ -190,3 +136,20 @@ border-top: 1px solid #eee; padding-top: 20px; } +/* Responsive design */ +@media (width <= 800px) { + body { + display: block; + } + #TOC { + position: static; + width: 100%; + height: auto; + border-right: none; + border-bottom: 1px solid #ddd; + } + main { + margin-left: 0; + padding: 20px; + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/etc/openqa/openqa.ini new/openQA-5.1777617029.fd9e1e69/etc/openqa/openqa.ini --- old/openQA-5.1777474261.55e5cd5e/etc/openqa/openqa.ini 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/etc/openqa/openqa.ini 2026-05-01 08:30:29.000000000 +0200 @@ -510,3 +510,11 @@ #casedir = https://github.com/os-autoinst/os-autoinst-distri-example.git #distri = example #build = openqa + +## Configuration for automatically adding worker classes to jobs based on patterns +[worker_class_auto_assignment] +## Automatically add a worker class to a job if no existing worker class matches the pattern. +## Format: pattern -> class +## The pattern is a regex. +## This can be specified multiple times. +#add_worker_class_if_missing = size-.* -> size-large diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/CLI.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/CLI.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/CLI.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/CLI.pm 2026-05-01 08:30:29.000000000 +0200 @@ -62,7 +62,6 @@ } sub _print_options ($label, $options) { - my @rows; my @getopts; for my $option (@$options) { my ($spec, $desc); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/CacheService/Model/Cache.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/CacheService/Model/Cache.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/CacheService/Model/Cache.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/CacheService/Model/Cache.pm 2026-05-01 08:30:29.000000000 +0200 @@ -16,7 +16,7 @@ use Time::HiRes qw(gettimeofday); # Only consider files larger than 250 MB for metrics (rates for smaller files are unrealistic) -use constant METRICS_DOWNLOAD_SIZE => $ENV{OPENQA_METRICS_DOWNLOAD_SIZE} // 262144000; +use constant METRICS_DOWNLOAD_SIZE => $ENV{OPENQA_METRICS_DOWNLOAD_SIZE} // 262_144_000; has downloader => sub { OpenQA::Downloader->new }; has [qw(location log sqlite min_free_percentage)]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Config.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Config.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Config.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Config.pm 2026-05-01 08:30:29.000000000 +0200 @@ -6,10 +6,12 @@ use Config::IniFiles; use Exporter qw(import); -use OpenQA::Log qw(log_info); +use OpenQA::Log qw(log_info log_warning); use Mojo::File qw(path); +use Feature::Compat::Try; -our @EXPORT = qw(config_dir_within_app_home lookup_config_files parse_config_files parse_config_files_as_hash); +our @EXPORT + = qw(config_dir_within_app_home lookup_config_files parse_config_files parse_config_files_as_hash parse_worker_class_auto_assignment); sub _config_dirs ($config_dir_within_home) { return [[$ENV{OPENQA_CONFIG} // ()], [$config_dir_within_home // ()], ['/etc/openqa', '/usr/etc/openqa']]; @@ -17,6 +19,33 @@ sub config_dir_within_app_home ($app) { $app->child('etc', 'openqa') } +sub _parse_auto_assignment_entry ($entry) { + my ($pattern_str, $class) = split /\s*->\s*/, $entry, 2; + if (!defined $class) { + log_warning("Missing delimiter ' -> ' in worker_class_auto_assignment: $entry"); + return (); + } + my $pattern_regex; + try { $pattern_regex = qr/$pattern_str/ } + catch ($e) { + log_warning("Invalid regex pattern in worker_class_auto_assignment: $pattern_str"); + return (); + } + return {pattern => $pattern_regex, class => $class}; +} + +sub parse_worker_class_auto_assignment ($config) { + return $config->{_worker_class_auto_assignment_rules} if $config->{_worker_class_auto_assignment_rules}; + + my $section = $config->{worker_class_auto_assignment} or return []; + my $entries = $section->{add_worker_class_if_missing} or return []; + $entries = [$entries] unless ref $entries eq 'ARRAY'; + + my $rules = [map { _parse_auto_assignment_entry($_) } grep { $_ } @$entries]; + $config->{_worker_class_auto_assignment_rules} = $rules; + return $rules; +} + sub lookup_config_files ($config_dir_within_home, $name, $silent = 0) { my $config_name; my @config_file_paths; @@ -32,7 +61,7 @@ last if $has_main_config; } if (@config_file_paths) { - log_info "Reading $config_name config from: @config_file_paths" unless $silent; + log_info("Reading $config_name config from: @config_file_paths") unless $silent; last; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Downloader.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Downloader.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Downloader.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Downloader.pm 2026-05-01 08:30:29.000000000 +0200 @@ -20,11 +20,13 @@ has res => undef; sub download ($self, $url, $target, $options = {}) { - my $log = $self->log; - - local $ENV{MOJO_TMPDIR} = $self->tmpdir; + my $tmpdir = path($self->tmpdir); + try { $tmpdir->make_path } + catch ($e) { return "Unable to create temporary directory: $e" } + local $ENV{MOJO_TMPDIR} = $tmpdir; my $remaining_attempts = $self->attempts; + my $log = $self->log; my ($code, $err); while (1) { $options->{on_attempt}->() if $options->{on_attempt}; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Parser/Format/JUnit.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Parser/Format/JUnit.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Parser/Format/JUnit.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Parser/Format/JUnit.pm 2026-05-01 08:30:29.000000000 +0200 @@ -38,7 +38,6 @@ my $dom = Mojo::DOM->new($xml); confess 'Failed parsing XML' if @{$dom->tree} <= 2; - my @tests; for my $ts ($dom->find('testsuite')->each) { my $ts_category = $ts->{package}; my $script = $ts->{script} ? $ts->{script} : undef; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Parser/Format/XUnit.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Parser/Format/XUnit.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Parser/Format/XUnit.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Parser/Format/XUnit.pm 2026-05-01 08:30:29.000000000 +0200 @@ -17,7 +17,6 @@ confess 'No XML given/loaded' unless $xml; my $dom = Mojo::DOM->new->xml(1)->parse($xml); - my @tests; my %t_names; my $i = 1; for my $ts ($dom->find('testsuite')->each) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Schema/Result/Jobs.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Schema/Result/Jobs.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Schema/Result/Jobs.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Schema/Result/Jobs.pm 2026-05-01 08:30:29.000000000 +0200 @@ -1423,7 +1423,7 @@ # Perform weak check on the number of bytes if the file size is > 250 MB my $temp_final_str = $temp_final_file->to_string; my ($sum, $real_sum) - = $chunk->end > 250000000 + = $chunk->end > 250_000_000 ? ($chunk->end, -s $temp_final_str) : ($chunk->total_cksum, $chunk->file_digest($temp_final_str)); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Schema/ResultSet/AuditEvents.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Schema/ResultSet/AuditEvents.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Schema/ResultSet/AuditEvents.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Schema/ResultSet/AuditEvents.pm 2026-05-01 08:30:29.000000000 +0200 @@ -25,7 +25,6 @@ ); sub delete_expired_entries ($self, %options) { - my @event_type_globs; my @queries; my $other_time_constraint; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Schema/ResultSet/Jobs.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Schema/ResultSet/Jobs.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Schema/ResultSet/Jobs.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Schema/ResultSet/Jobs.pm 2026-05-01 08:30:29.000000000 +0200 @@ -9,6 +9,7 @@ use Encode qw(decode_utf8); use File::Basename 'basename'; use IPC::Run; +use OpenQA::Config; use OpenQA::App; use OpenQA::Jobs::Constants; use OpenQA::Constants qw(DEFAULT_MAX_JOB_TIME); @@ -24,7 +25,7 @@ use Mojolicious::Validator::Validation; use Time::HiRes 'time'; use DateTime; -use List::Util qw(any); +use List::Util qw(any uniq); use Scalar::Util qw(looks_like_number); =head2 latest_build @@ -102,6 +103,35 @@ return @latest; } +sub _apply_auto_worker_class_assignment ($settings_ref) { + my $app = OpenQA::App->singleton or return undef; + my $rules = OpenQA::Config::parse_worker_class_auto_assignment($app->config // {}); + return unless @$rules; + + my @existing = grep { $_ } split qr/,/, $settings_ref->{WORKER_CLASS} // ''; + my @to_add = grep { + my $pattern = $_->{pattern}; + !any { $_ =~ $pattern } @existing; + } @$rules; + return unless @to_add; + + for my $rule (@to_add) { + log_info("Auto-assigning worker class '$rule->{class}' to job (no match for pattern '$rule->{pattern}')"); + } + $settings_ref->{WORKER_CLASS} = join ',', sort(uniq(@existing, map { $_->{class} } @to_add)); +} + +sub _build_job_settings ($settings_ref) { + my $now = now; + my @job_settings; + for my $key (keys %$settings_ref) { + my $val = $settings_ref->{$key}; + push @job_settings, map { {t_created => $now, t_updated => $now, key => $key, value => $_} } + grep { defined $_ } $key =~ qr/(^WORKER_CLASS|\[\])$/ ? split(m/,/, $val // '') : ($val); + } + return \@job_settings; +} + sub create_from_settings ($self, $settings, $scheduled_product_id = undef) { my %settings = %$settings; my %new_job_args; @@ -112,8 +142,8 @@ # validate special settings my %special_settings = (TEST => delete $settings{TEST}, _PRIORITY => delete $settings{_PRIORITY}); - my $validator = Mojolicious::Validator->new; - my $v = Mojolicious::Validator::Validation->new(validator => $validator, input => \%special_settings); + my $v + = Mojolicious::Validator::Validation->new(validator => Mojolicious::Validator->new, input => \%special_settings); my $test = $v->required('TEST')->like(TEST_NAME_REGEX)->param; my $prio = $v->optional('_PRIORITY')->num->param; die 'The following settings are invalid: ' . join(', ', @{$v->failed}) . "\n" if $v->has_error; @@ -141,10 +171,9 @@ if (my $value = delete $settings{$key}) { $new_job_args{$key} = $value } } - # assign default for WORKER_CLASS $settings{WORKER_CLASS} ||= 'qemu_' . ($new_job_args{ARCH} // 'x86_64'); + _apply_auto_worker_class_assignment(\%settings); - # assign scheduled product $new_job_args{scheduled_product_id} = $scheduled_product_id; my $debug_msg = $self->_apply_prio_throttling(\%settings, \%new_job_args, $group); @@ -153,16 +182,7 @@ my $job = $self->create(\%new_job_args); log_debug(sprintf "(Job %d) $debug_msg", $job->id) if $debug_msg; - # add job settings - my @job_settings; - my $now = now; - for my $key (keys %settings) { - my @values = $key =~ qr/(^WORKER_CLASS|\[\])$/ ? split(m/,/, $settings{$key}) : ($settings{$key}); - push @job_settings, {t_created => $now, t_updated => $now, key => $key, value => $_} for @values; - } - $job->settings->populate(\@job_settings); - - # associate currently available assets with job + $job->settings->populate(_build_job_settings(\%settings)); $job->register_assets_from_settings; log_info('Ignoring invalid group ' . encode_json($group_args) . ' when creating new job ' . $job->id) @@ -171,6 +191,7 @@ return $job; } + sub _update_priority ($value, $throt_config, $job_args) { my $scale = $throt_config->{scale} // 0; my $reference = $throt_config->{reference} // 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/WebAPI/Controller/API/V1/Job.pm 2026-05-01 08:30:29.000000000 +0200 @@ -970,7 +970,6 @@ my $schema = $self->schema; my %settings; # Machines, product and test suite settings for the job - my @classes; # Populated with WORKER_CLASS settings from machines and products my %params = (input_args => $args, settings => \%settings); # Populated with Product settings if there are DISTRI, VERSION, FLAVOR, ARCH in arguments. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Worker/Job.pm new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Worker/Job.pm --- old/openQA-5.1777474261.55e5cd5e/lib/OpenQA/Worker/Job.pm 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/lib/OpenQA/Worker/Job.pm 2026-05-01 08:30:29.000000000 +0200 @@ -976,7 +976,7 @@ my $local_upload = $global_settings->{LOCAL_UPLOAD} // 1; my $ua = $self->client->ua; my @channels_worker_only = ('worker'); - my @channels_both = ('autoinst', 'worker'); + my @channels_both = qw(autoinst worker); my $error; log_info("Uploading $filename using multiple chunks", channels => \@channels_worker_only, default => 1); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/package-lock.json new/openQA-5.1777617029.fd9e1e69/package-lock.json --- old/openQA-5.1777474261.55e5cd5e/package-lock.json 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/package-lock.json 2026-05-01 08:30:29.000000000 +0200 @@ -24,7 +24,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@stylistic/stylelint-plugin": "^5.0.1", - "eslint": "^10.1.0", + "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "globals": "^17.4.0", @@ -325,13 +325,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.23.3", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", - "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^3.0.3", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" }, @@ -340,22 +340,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", - "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.1" + "@eslint/core": "^1.2.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", - "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -387,9 +387,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", - "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -397,13 +397,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", - "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^1.1.1", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { @@ -1421,18 +1421,18 @@ } }, "node_modules/eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz", - "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.1.tgz", + "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.3", - "@eslint/config-helpers": "^0.5.3", - "@eslint/core": "^1.1.1", - "@eslint/plugin-kit": "^0.6.1", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/package.json new/openQA-5.1777617029.fd9e1e69/package.json --- old/openQA-5.1777474261.55e5cd5e/package.json 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/package.json 2026-05-01 08:30:29.000000000 +0200 @@ -3,8 +3,8 @@ "version": "1.0.0", "description": "Testing Framework", "scripts": { - "lint": "eslint \"assets/javascripts/**/*.js\" && stylelint \"assets/stylesheets/**/*.{scss,css}\"", - "fix": "eslint --fix \"assets/javascripts/**/*.js\" && stylelint --fix \"assets/stylesheets/**/*.{scss,css}\"" + "lint": "eslint \"assets/javascripts/**/*.js\" && stylelint \"assets/stylesheets/**/*.{scss,css}\" \"docs/*.css\"", + "fix": "eslint --fix \"assets/javascripts/**/*.js\" && stylelint --fix \"assets/stylesheets/**/*.{scss,css}\" \"docs/*.css\"" }, "license": "GPL-2.0", "bugs": { @@ -14,7 +14,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@stylistic/stylelint-plugin": "^5.0.1", - "eslint": "^10.1.0", + "eslint": "^10.2.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "globals": "^17.4.0", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/profiles/apparmor.d/usr.share.openqa.script.worker new/openQA-5.1777617029.fd9e1e69/profiles/apparmor.d/usr.share.openqa.script.worker --- old/openQA-5.1777474261.55e5cd5e/profiles/apparmor.d/usr.share.openqa.script.worker 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/profiles/apparmor.d/usr.share.openqa.script.worker 2026-05-01 08:30:29.000000000 +0200 @@ -117,6 +117,7 @@ /usr/bin/isotovideo rix, /usr/bin/isoinfo rix, /usr/bin/jq rix, + /usr/bin/jsonnet rix, /usr/bin/podman rix, /usr/bin/lscpu rCx, /{usr/,}bin/mkdir rix, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/05-scheduler-capabilities.t new/openQA-5.1777617029.fd9e1e69/t/05-scheduler-capabilities.t --- old/openQA-5.1777474261.55e5cd5e/t/05-scheduler-capabilities.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/05-scheduler-capabilities.t 2026-05-01 08:30:29.000000000 +0200 @@ -13,6 +13,8 @@ use OpenQA::WebAPI::Controller::API::V1::Worker; use Test::Mojo; use Test::Warnings ':report_warnings'; +use Test::MockModule; +use Test::MockObject; use Mojo::Util 'monkey_patch'; use OpenQA::Test::TimeLimit '10'; @@ -203,4 +205,33 @@ # job G is not grabbed because there is no worker with class 'special' +subtest 'auto_worker_class' => sub { + my $mock_app = Test::MockModule->new('OpenQA::App', no_auto => 1); + my $mock_app_obj = Test::MockObject->new; + $mock_app_obj->set_always( + config => { + global => {worker_timeout => 60}, + misc_limits => { + throttle_failing_job_threshold => 0, + throttle_failing_job_prio_step => 0, + throttle_failing_job_history_length => 0, + }, + worker_class_auto_assignment => { + add_worker_class_if_missing => 'size-.* -> size-large' + }}); + my $mock_log = Test::MockObject->new; + $mock_log->set_always(info => 1); + $mock_log->set_always(level => 'info'); + $mock_app_obj->set_always(log => $mock_log); + $mock_app->redefine(singleton => $mock_app_obj); + + my $job = job_create({TEST => 'auto_class_test', ARCH => 'x86_64'}); + my @classes = sort map { $_->value } $job->settings->search({key => 'WORKER_CLASS'})->all; + is_deeply \@classes, ['qemu_x86_64', 'size-large'], 'auto-assigned size-large'; + + $job = job_create({TEST => 'auto_class_test_existing', ARCH => 'x86_64', WORKER_CLASS => 'size-small,qemu_x86_64'}); + @classes = sort map { $_->value } $job->settings->search({key => 'WORKER_CLASS'})->all; + is_deeply \@classes, ['qemu_x86_64', 'size-small'], 'kept existing size-small'; +}; + done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/05-scheduler-dependencies.t new/openQA-5.1777617029.fd9e1e69/t/05-scheduler-dependencies.t --- old/openQA-5.1777474261.55e5cd5e/t/05-scheduler-dependencies.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/05-scheduler-dependencies.t 2026-05-01 08:30:29.000000000 +0200 @@ -157,7 +157,7 @@ is $scheduled_jobs->{99928}->{priority}, 46, 'regular prio for job 99928 assumed'; is $scheduled_jobs->{$_}->{priority_offset}, 0, "job $_ not deprioritized yet" for (99927, 99928); combined_like { $scheduler->schedule } - qr/Unable to serialize directly chained job sequence of 9992(7|8): detected cycle at 9992(7|8)/, + qr/Unable to serialize directly chained job sequence of 9992[78]: detected cycle at 9992[78]/, 'info about cycle logged'; $scheduler->_update_scheduled_jobs; # apply deprioritization is $scheduled_jobs->{99927}->{priority}, 46, 'reduced prio for job 99927 assumed'; @@ -1290,7 +1290,7 @@ my $free_workers = OpenQA::Scheduler::Model::Jobs::determine_free_workers(); my $allocated_workers; combined_like { $allocated_workers = OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($free_workers) } - qr/Need to schedule 3 parallel jobs for job 1.*Discarding job (1|2|3).*Discarding job (1|2|3)/s, + qr/Need to schedule 3 parallel jobs for job 1.*Discarding job [123].*Discarding job [123]/s, 'discarding jobs due to incomplete parallel cluster'; is $mocked_jobs{1}->{priority_offset}, 10, 'priority of parallel parent increased (once per child)'; is_deeply $allocated_workers, {}, 'no workers "held" so far while still increased prio' @@ -1299,7 +1299,7 @@ # run the scheduler again assuming highest prio for parallel parent; worker supposed to be "held" $mocked_jobs{1}->{priority} = 0; combined_like { ($allocated_workers) = OpenQA::Scheduler::Model::Jobs->singleton->_allocate_jobs($free_workers) } - qr/Holding worker .* for job (1|2|3) to avoid starvation.*Holding worker .* for job (1|2|3) to avoid starvation/s, + qr/Holding worker .* for job [123] to avoid starvation.*Holding worker .* for job [123] to avoid starvation/s, 'holding 2 workers (for 2 of our parallel jobs while 3rd worker is unavailable)'; is_deeply [sort keys %$allocated_workers], [map { $_->id } @mocked_free_workers], 'both free workers "held"'; ok $_ >= 1 && $_ <= 3, "worker held for expected job ($_)" for values %$allocated_workers; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/16-utils.t new/openQA-5.1777617029.fd9e1e69/t/16-utils.t --- old/openQA-5.1777474261.55e5cd5e/t/16-utils.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/16-utils.t 2026-05-01 08:30:29.000000000 +0200 @@ -81,20 +81,21 @@ }; subtest 'download speed' => sub { - is download_rate([1638459407, 528237], [1638459408, 628237], 1024), '930.91'; - is download_rate([1638459407, 528237], [1638459408, 628237], 1024 * 1024 * 1024), '976128930.91'; - is download_rate([1638459407, 528237], [1638459407, 528237], 1024), undef; - - is download_speed([1638459407, 528237], [1638459408, 628237], 1024), '930.91 Byte/s'; - is download_speed([1638459407, 528237], [1638459408, 628237], 1024 * 1024), '931 KiB/s'; - is download_speed([1638459407, 528237], [1638459408, 628237], 1024 * 1024 * 1024), '931 MiB/s'; - is download_speed([1638459407, 528237], [1638459408, 628237], 1024 * 1024 * 1024 * 1024), '931 GiB/s'; - is download_speed([1638459407, 528237], [1638459408, 628237], 1024 * 1024 * 1024 * 1024 * 1024), '953251 GiB/s'; + is download_rate([1_638_459_407, 528237], [1_638_459_408, 628237], 1024), '930.91'; + is download_rate([1_638_459_407, 528237], [1_638_459_408, 628237], 1024 * 1024 * 1024), '976128930.91'; + is download_rate([1_638_459_407, 528237], [1_638_459_407, 528237], 1024), undef; + + is download_speed([1_638_459_407, 528237], [1_638_459_408, 628237], 1024), '930.91 Byte/s'; + is download_speed([1_638_459_407, 528237], [1_638_459_408, 628237], 1024 * 1024), '931 KiB/s'; + is download_speed([1_638_459_407, 528237], [1_638_459_408, 628237], 1024 * 1024 * 1024), '931 MiB/s'; + is download_speed([1_638_459_407, 528237], [1_638_459_408, 628237], 1024 * 1024 * 1024 * 1024), '931 GiB/s'; + is download_speed([1_638_459_407, 528237], [1_638_459_408, 628237], 1024 * 1024 * 1024 * 1024 * 1024), + '953251 GiB/s'; - is download_speed([1638459407, 528237], [1638459508, 628237], 1024 * 1024 * 512), '5.1 MiB/s'; - is download_speed([1638459407, 528237], [1638459407, 588237], 123456789), '1.9 GiB/s'; + is download_speed([1_638_459_407, 528237], [1_638_459_508, 628237], 1024 * 1024 * 512), '5.1 MiB/s'; + is download_speed([1_638_459_407, 528237], [1_638_459_407, 588237], 123_456_789), '1.9 GiB/s'; - is download_speed([1638459407, 528237], [1638459407, 528237], 1024), '??/s'; + is download_speed([1_638_459_407, 528237], [1_638_459_407, 528237], 1024), '??/s'; }; subtest 'labels' => sub { @@ -152,7 +153,7 @@ $t3 => sub { my ($k, $v, $ks, $what) = @_; next if reftype $what eq 'HASH' && exists $what->{_data}; - like $_[0], qr/bar|baz|foo|0|1|fish$|fish2|boring/, 'Walked'; + like $_[0], qr/bar|baz|foo|[01]|fish$|fish2|boring/, 'Walked'; $what->[$k] = {_type => ref $v, _data => $v} if reftype $what eq 'ARRAY'; $what->{$k} = {_type => ref $v, _data => $v} if reftype $what eq 'HASH'; @@ -534,10 +535,10 @@ subtest 'human readable size' => sub { is human_readable_size(0), '0 Byte', 'zero'; is human_readable_size(1), '1 Byte', 'one'; - is human_readable_size(13443399680), '13 GiB', 'two digits GB'; - is human_readable_size(8007188480), '7.5 GiB', 'smaller GB'; - is human_readable_size(-8007188480), '-7.5 GiB', 'negative smaller GB'; - is human_readable_size(717946880), '685 MiB', 'large MB'; + is human_readable_size(13_443_399_680), '13 GiB', 'two digits GB'; + is human_readable_size(8_007_188_480), '7.5 GiB', 'smaller GB'; + is human_readable_size(-8_007_188_480), '-7.5 GiB', 'negative smaller GB'; + is human_readable_size(717_946_880), '685 MiB', 'large MB'; is human_readable_size(245760), '240 KiB', 'less than a MB'; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/17-build_tagging.t new/openQA-5.1777617029.fd9e1e69/t/17-build_tagging.t --- old/openQA-5.1777474261.55e5cd5e/t/17-build_tagging.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/17-build_tagging.t 2026-05-01 08:30:29.000000000 +0200 @@ -59,7 +59,6 @@ }; sub create_job_version_build ($version, $build) { - my %job_hash; $job_hash->{VERSION} = $version; $job_hash->{BUILD} = $build; $jobs->create($job_hash); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/25-cache-service.t new/openQA-5.1777617029.fd9e1e69/t/25-cache-service.t --- old/openQA-5.1777474261.55e5cd5e/t/25-cache-service.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/25-cache-service.t 2026-05-01 08:30:29.000000000 +0200 @@ -101,8 +101,8 @@ my $dir2 = tempdir; my $rsync_request = $cache_client->rsync_request(from => $dir, to => $dir2); - my $t_dir = int rand 13432432; - my $data = int rand 348394280934820842093; + my $t_dir = int rand 13_432_432; + my $data = int rand 348_394_280_934_820_842_093; $dir->child($t_dir)->spew($data); my $expected = $dir2->child('tests')->child($t_dir); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/25-downloader.t new/openQA-5.1777617029.fd9e1e69/t/25-downloader.t --- old/openQA-5.1777474261.55e5cd5e/t/25-downloader.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/25-downloader.t 2026-05-01 08:30:29.000000000 +0200 @@ -5,6 +5,7 @@ use Test::Most; use Test::Warnings ':report_warnings'; +use Mojo::Base -signatures; use utf8; use FindBin; use lib "$FindBin::Bin/lib", "$FindBin::Bin/../external/os-autoinst-common/lib"; @@ -20,6 +21,7 @@ use OpenQA::Test::Utils qw(fake_asset_server wait_for_or_bail_out); use OpenQA::Test::TimeLimit '10'; use Mojo::File qw(tempdir); +use Test::MockModule; my $port = Mojo::IOLoop::Server->generate_port; my $host = "127.0.0.1:$port"; @@ -67,6 +69,12 @@ $ua->connect_timeout(0.25)->inactivity_timeout(0.25); +subtest 'Unable to create temporary directory' => sub { + my $file_mock = Test::MockModule->new('Mojo::File'); + $file_mock->redefine(make_path => sub ($self) { die 'fake error' }); + like $downloader->download('from', 'to'), qr/temp.*dir.*fake error/, 'error returned'; +}; + subtest 'Connection refused' => sub { my $from = "http://$host/tests/922756/asset/hdd/[email protected]"; like $downloader->download($from, $to), qr/Download of "$to" failed: Connection refused/, 'Failed'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/32-openqa_client.t new/openQA-5.1777617029.fd9e1e69/t/32-openqa_client.t --- old/openQA-5.1777474261.55e5cd5e/t/32-openqa_client.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/32-openqa_client.t 2026-05-01 08:30:29.000000000 +0200 @@ -18,10 +18,10 @@ plan skip_all => 'set HEAVY=1 to execute (takes longer)' unless $ENV{HEAVY}; OpenQA::Test::Case->new->init_data(fixtures_glob => '01-jobs.pl 02-workers.pl 03-users.pl'); -my $chunk_size = 10000000; +my $chunk_size = 10_000_000; # allow up to 200MB - videos mostly -$ENV{MOJO_MAX_MESSAGE_SIZE} = 207741824; +$ENV{MOJO_MAX_MESSAGE_SIZE} = 207_741_824; my $tempdir = tempdir("$FindBin::Script-XXXX", TMPDIR => 1); chdir $tempdir; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/34-developer_mode-unit.t new/openQA-5.1777617029.fd9e1e69/t/34-developer_mode-unit.t --- old/openQA-5.1777474261.55e5cd5e/t/34-developer_mode-unit.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/34-developer_mode-unit.t 2026-05-01 08:30:29.000000000 +0200 @@ -35,7 +35,7 @@ my @ipc_messages_for_websocket_server; my $fake_send_msg_failure; my $mock_client = Test::MockModule->new('OpenQA::WebSockets::Client'); -my ($client_called, $last_command); +my $client_called; $mock_client->redefine( send_msg => sub { my $self = shift; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/api/02-iso.t new/openQA-5.1777617029.fd9e1e69/t/api/02-iso.t --- old/openQA-5.1777474261.55e5cd5e/t/api/02-iso.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/api/02-iso.t 2026-05-01 08:30:29.000000000 +0200 @@ -512,7 +512,7 @@ is $res->json->{count}, 0, 'no jobs scheduled if cycle detected'; like $res->json->{failed}->[0]->{error_messages}->[0], - qr/There is a cycle in the dependencies of chained-(a|b|c|d)/, + qr/There is a cycle in the dependencies of chained-[abcd]/, 'cycle reported'; $schema->txn_rollback; }; @@ -583,7 +583,7 @@ my $res = schedule_iso($t, {%iso, _GROUP => 'opensuse test'}); is $res->json->{count}, 0, 'none of the jobs has been scheduled'; - like $_->{error_messages}->[0], qr/chained-(c|d|e) \(bar,baz\) does not match .* \(foo\)/, 'error reported' + like $_->{error_messages}->[0], qr/chained-[cde] \(bar,baz\) does not match .* \(foo\)/, 'error reported' for @{$res->json->{failed}}; $schema->txn_rollback; }; @@ -816,7 +816,7 @@ my $res = schedule_iso($t, {scheduled_product_clone_id => 'foobar'}, 400); like $res->body, qr/scheduled_product_id.*invalid/, 'error returned if scheduled product to clone from is invalid'; - $res = schedule_iso($t, {scheduled_product_clone_id => 1234567}, 404); + $res = schedule_iso($t, {scheduled_product_clone_id => 1_234_567}, 404); like $res->body, qr/to clone.*not found/, 'error returned if scheduled product to clone from does not exist'; $res = schedule_iso($t, {scheduled_product_clone_id => $scheduled_product_id}, 200, {async => 1}); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/api/04-jobs.t new/openQA-5.1777617029.fd9e1e69/t/api/04-jobs.t --- old/openQA-5.1777474261.55e5cd5e/t/api/04-jobs.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/api/04-jobs.t 2026-05-01 08:30:29.000000000 +0200 @@ -48,7 +48,7 @@ my @data = ("[audit]\n", "blocklist = job_grab\n"); $tempdir->child('openqa.ini')->spew(join '', @data); -my $chunk_size = 10000000; +my $chunk_size = 10_000_000; my $io_loop_mock = mock_io_loop(subprocess => 1); @@ -60,7 +60,7 @@ } # allow up to 200MB - videos mostly -$ENV{MOJO_MAX_MESSAGE_SIZE} = 207741824; +$ENV{MOJO_MAX_MESSAGE_SIZE} = 207_741_824; my $t = client(Test::Mojo->new('OpenQA::WebAPI')); my $cfg = $t->app->config; @@ -333,7 +333,7 @@ $t->get_ok('/api/v1/job_settings/jobs' => form => {key => '*_TEST_ISSUES', list_value => 26103})->status_is(200) ->json_is({jobs => []}); - local $t->app->config->{misc_limits}{job_settings_max_recent_jobs} = 1000000000; + local $t->app->config->{misc_limits}{job_settings_max_recent_jobs} = 1_000_000_000; $t->get_ok('/api/v1/job_settings/jobs' => form => {key => '*_TEST_ISSUES', list_value => 26110})->status_is(200) ->json_is({jobs => [99981]}); $t->get_ok('/api/v1/job_settings/jobs' => form => {key => '*_TEST_ISSUES', list_value => 26103})->status_is(200) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/api/08-jobtemplates.t new/openQA-5.1777617029.fd9e1e69/t/api/08-jobtemplates.t --- old/openQA-5.1777474261.55e5cd5e/t/api/08-jobtemplates.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/api/08-jobtemplates.t 2026-05-01 08:30:29.000000000 +0200 @@ -257,7 +257,6 @@ my $yaml2 = path("$FindBin::Bin/../data/08-opensuse-test.yaml")->slurp; my %yaml = (1001 => $yaml1, 1002 => $yaml2); - my @templates; for my $group ($job_groups->search) { my $id = $group->id; my $yaml = $group->to_yaml; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/api/17-mcp.t new/openQA-5.1777617029.fd9e1e69/t/api/17-mcp.t --- old/openQA-5.1777474261.55e5cd5e/t/api/17-mcp.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/api/17-mcp.t 2026-05-01 08:30:29.000000000 +0200 @@ -136,7 +136,8 @@ subtest 'Job does not exist' => sub { local $t->app->config->{misc_limits}{mcp_max_result_size} = 100; - my $result = $client->call_tool('openqa_get_log_file', {job_id => 999999999, file_name => 'autoinst-log.txt'}); + my $result + = $client->call_tool('openqa_get_log_file', {job_id => 999_999_999, file_name => 'autoinst-log.txt'}); ok $result->{isError}, 'is an error'; my $text = $result->{content}[0]{text}; like $text, qr/Job does not exist/, 'error message'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/config.t new/openQA-5.1777617029.fd9e1e69/t/config.t --- old/openQA-5.1777474261.55e5cd5e/t/config.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/config.t 2026-05-01 08:30:29.000000000 +0200 @@ -358,6 +358,42 @@ qr/Wrong format/, 'warning logged'; }; }; + + subtest 'parse_worker_class_auto_assignment' => sub { + my $config = { + worker_class_auto_assignment => { + add_worker_class_if_missing => + ['size-.* -> size-large', 'gpu-.* -> gpu-none', 'invalid line', 'bad regex ( -> size-error',]}}; + + my $mock_config = Test::MockModule->new('OpenQA::Config'); + my @warnings; + $mock_config->redefine(log_warning => sub { push @warnings, $_[0] }); + + my $rules = OpenQA::Config::parse_worker_class_auto_assignment($config); + + is scalar @$rules, 2, 'parsed 2 valid rules'; + is $rules->[0]{class}, 'size-large', 'first rule class'; + ok 'size-small' =~ $rules->[0]{pattern}, 'first rule pattern matches'; + + is $rules->[1]{class}, 'gpu-none', 'second rule class'; + ok 'gpu-anything' =~ $rules->[1]{pattern}, 'second rule pattern matches'; + + is scalar @warnings, 2, 'got 2 warnings for invalid lines'; + like $warnings[0], qr/Missing delimiter/, 'warning for missing delimiter'; + like $warnings[1], qr/Invalid regex pattern/, 'warning for invalid regex'; + + subtest 'single value' => sub { + my $config = {worker_class_auto_assignment => {add_worker_class_if_missing => 'size-.* -> size-large'}}; + my $rules = OpenQA::Config::parse_worker_class_auto_assignment($config); + is scalar @$rules, 1, 'parsed 1 rule from single value'; + }; + + subtest 'empty' => sub { + is_deeply OpenQA::Config::parse_worker_class_auto_assignment({}), [], 'empty config returns empty array'; + is_deeply OpenQA::Config::parse_worker_class_auto_assignment({worker_class_auto_assignment => {}}), [], + 'empty section returns empty array'; + }; + }; }; done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/fixtures/01-jobs.pl new/openQA-5.1777617029.fd9e1e69/t/fixtures/01-jobs.pl --- old/openQA-5.1777474261.55e5cd5e/t/fixtures/01-jobs.pl 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/fixtures/01-jobs.pl 2026-05-01 08:30:29.000000000 +0200 @@ -61,7 +61,7 @@ settings => [ {key => 'DESKTOP', value => 'minimalx'}, {key => 'ISO', value => 'openSUSE-Factory-staging_e-x86_64-Build87.5011-Media.iso'}, - {key => 'ISO_MAXSIZE', value => 737280000}, + {key => 'ISO_MAXSIZE', value => 737_280_000}, {key => 'ISO_1', value => 'openSUSE-Factory-staging_e-x86_64-Build87.5011-Media.iso'}, {key => 'BASE_TEST_ISSUES', value => '26103'} ], @@ -429,7 +429,7 @@ priority => 50, result => 'skipped', state => 'cancelled', - t_created => time2str('%Y-%m-%d %H:%M:%S', time - 3000100, 'UTC'), + t_created => time2str('%Y-%m-%d %H:%M:%S', time - 3_000_100, 'UTC'), t_started => undef, t_finished => undef, TEST => 'RAID0', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/ui/18-tests-details.t new/openQA-5.1777617029.fd9e1e69/t/ui/18-tests-details.t --- old/openQA-5.1777474261.55e5cd5e/t/ui/18-tests-details.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/ui/18-tests-details.t 2026-05-01 08:30:29.000000000 +0200 @@ -238,11 +238,11 @@ like $url[0], qr{bugzilla.*enter_bug.*tests%2F99937}, 'bugzilla link referencing current test'; like $url[0], qr{in\+scenario\+%60opensuse-13\.1-DVD-i586-kde}, 'bugzilla link contains scenario marked verbatim'; like $url[1], qr{bugzilla.*enter_bug}, 'kernel product bug uses bugzilla enter_bug'; - like $url[1], qr{(?:\?|&)product=openSUSE\+Distribution(?:&|$)}, 'kernel product bug sets product'; + like $url[1], qr{[?&]product=openSUSE\+Distribution(?:&|$)}, 'kernel product bug sets product'; like $url[1], - qr{(?:\?|&)short_desc=%5BQE%5D%5BBuild\+0091%5D\+Kernel\+test\+fails\+in\+bootloader}, + qr{[?&]short_desc=%5BQE%5D%5BBuild\+0091%5D\+Kernel\+test\+fails\+in\+bootloader}, 'kernel short_desc correct'; - like $url[1], qr{(?:\?|&)comment=}, 'kernel product bug has comment template'; + like $url[1], qr{[?&]comment=}, 'kernel product bug has comment template'; like $url[2], qr{progress.*new}, 'progress/redmine link for reporting test issues'; like $url[2], qr{in\+scenario\+%60opensuse-13\.1-DVD-i586-kde}, 'progress/redmine link contains scenario'; like $url[2], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/ui/26-jobs_restart.t new/openQA-5.1777617029.fd9e1e69/t/ui/26-jobs_restart.t --- old/openQA-5.1777474261.55e5cd5e/t/ui/26-jobs_restart.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/ui/26-jobs_restart.t 2026-05-01 08:30:29.000000000 +0200 @@ -24,7 +24,7 @@ sub prepare_database () { # Populate more cluster jobs - my @test_names = ('create_hdd', 'support_server', 'master_node', 'slave_node'); + my @test_names = qw(create_hdd support_server master_node slave_node); for my $n (0 .. 3) { my $new = { id => 99900 + $n, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1777474261.55e5cd5e/t/ui/27-plugin_obs_rsync_obs_status.t new/openQA-5.1777617029.fd9e1e69/t/ui/27-plugin_obs_rsync_obs_status.t --- old/openQA-5.1777474261.55e5cd5e/t/ui/27-plugin_obs_rsync_obs_status.t 2026-04-29 16:51:01.000000000 +0200 +++ new/openQA-5.1777617029.fd9e1e69/t/ui/27-plugin_obs_rsync_obs_status.t 2026-05-01 08:30:29.000000000 +0200 @@ -224,7 +224,7 @@ }; subtest 'build service authentication: signature generation' => sub { - $mocked_time = 1664187470; + $mocked_time = 1_664_187_470; note 'time right now: ' . time; is time(), $mocked_time, 'Time is not frozen!'; is $helper->is_status_dirty('ProjTestingSignature'), 1, 'signature matches fixture'; ++++++ openQA.obsinfo ++++++ --- /var/tmp/diff_new_pack.tZPAXP/_old 2026-05-04 12:54:22.223586681 +0200 +++ /var/tmp/diff_new_pack.tZPAXP/_new 2026-05-04 12:54:22.231587010 +0200 @@ -1,5 +1,5 @@ name: openQA -version: 5.1777474261.55e5cd5e -mtime: 1777474261 -commit: 55e5cd5e9f3a12facb9f43e315f30b401a9279a9 +version: 5.1777617029.fd9e1e69 +mtime: 1777617029 +commit: fd9e1e6944c531b90a4edef6a15954e3390a1a3d
