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-02-21 21:02:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openQA (Old) and /work/SRC/openSUSE:Factory/.openQA.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openQA" Sat Feb 21 21:02:36 2026 rev:808 rq:1334235 version:5.1771589939.8f8502b4 Changes: -------- --- /work/SRC/openSUSE:Factory/openQA/openQA.changes 2026-02-20 17:51:33.253847033 +0100 +++ /work/SRC/openSUSE:Factory/.openQA.new.1977/openQA.changes 2026-02-21 21:04:37.747453993 +0100 @@ -1,0 +2,17 @@ +Fri Feb 20 17:09:47 UTC 2026 - [email protected] + +- Update to version 5.1771589939.8f8502b4: + * feat: Improve default `PRODUCTDIR` after ef229dc2 + * refactor(ui): simplify removal of empty query parameters (after rebase) + * test(45-make-update-deps): allow bypassing dependency update check + * fix(test): improve UI test robustness by using state-based waiting + * test: Improve workaround for candidates menu not opening sometimes + * docs: Mention use of relative paths in `CASEDIR` to avoid symlinking + * fix: Fix wrong uses of "checkout" that should be "check out" + * feat: support filtering by meta-results+states in /tests/overview + * Revert "feat: Add symlink for aeon in openqa-bootstrap script" + * fix(43-scheduling-and-worker-scalability): prevent sporadic issues + * refactor: use join for properties in determine_free_workers + * fix: do not cache websocket_api_version if not set + +------------------------------------------------------------------- Old: ---- openQA-5.1771473096.98530511.obscpio New: ---- openQA-5.1771589939.8f8502b4.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openQA-client-test.spec ++++++ --- /var/tmp/diff_new_pack.9RcvFQ/_old 2026-02-21 21:04:39.395521891 +0100 +++ /var/tmp/diff_new_pack.9RcvFQ/_new 2026-02-21 21:04:39.399522056 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-client Name: %{short_name}-test -Version: 5.1771473096.98530511 +Version: 5.1771589939.8f8502b4 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-devel-test.spec ++++++ --- /var/tmp/diff_new_pack.9RcvFQ/_old 2026-02-21 21:04:39.435523539 +0100 +++ /var/tmp/diff_new_pack.9RcvFQ/_new 2026-02-21 21:04:39.435523539 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-devel Name: %{short_name}-test -Version: 5.1771473096.98530511 +Version: 5.1771589939.8f8502b4 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA-test.spec ++++++ --- /var/tmp/diff_new_pack.9RcvFQ/_old 2026-02-21 21:04:39.475525187 +0100 +++ /var/tmp/diff_new_pack.9RcvFQ/_new 2026-02-21 21:04:39.475525187 +0100 @@ -18,7 +18,7 @@ %define short_name openQA Name: %{short_name}-test -Version: 5.1771473096.98530511 +Version: 5.1771589939.8f8502b4 Release: 0 Summary: Test package for openQA License: GPL-2.0-or-later ++++++ openQA-worker-test.spec ++++++ --- /var/tmp/diff_new_pack.9RcvFQ/_old 2026-02-21 21:04:39.503526341 +0100 +++ /var/tmp/diff_new_pack.9RcvFQ/_new 2026-02-21 21:04:39.507526506 +0100 @@ -18,7 +18,7 @@ %define short_name openQA-worker Name: %{short_name}-test -Version: 5.1771473096.98530511 +Version: 5.1771589939.8f8502b4 Release: 0 Summary: Test package for %{short_name} License: GPL-2.0-or-later ++++++ openQA.spec ++++++ --- /var/tmp/diff_new_pack.9RcvFQ/_old 2026-02-21 21:04:39.551528319 +0100 +++ /var/tmp/diff_new_pack.9RcvFQ/_new 2026-02-21 21:04:39.555528483 +0100 @@ -99,7 +99,7 @@ %define devel_requires %devel_no_selenium_requires chromedriver Name: openQA -Version: 5.1771473096.98530511 +Version: 5.1771589939.8f8502b4 Release: 0 Summary: The openQA web-frontend, scheduler and tools License: GPL-2.0-or-later ++++++ openQA-5.1771473096.98530511.obscpio -> openQA-5.1771589939.8f8502b4.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/assets/javascripts/filter_form.js new/openQA-5.1771589939.8f8502b4/assets/javascripts/filter_form.js --- old/openQA-5.1771473096.98530511/assets/javascripts/filter_form.js 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/assets/javascripts/filter_form.js 2026-02-20 13:18:59.000000000 +0100 @@ -32,7 +32,7 @@ // optimize results and states to use negative filters if shorter ['result', 'state'].forEach(key => { - const checkboxes = Array.from(filterForm.querySelectorAll(`input[name="${key}"]`)); + const checkboxes = Array.from(filterForm.querySelectorAll(`input[name="${key}"]:not(.filter-meta)`)); const checked = checkboxes.filter(cb => cb.checked); const unchecked = checkboxes.filter(cb => !cb.checked); if (checked.length > 1 && unchecked.length > 0 && unchecked.length < checked.length) { @@ -42,12 +42,31 @@ }); const params = new URLSearchParams(formData); - const keysToDelete = []; - params.forEach((val, key) => { - if (val === '') keysToDelete.push(key); - }); - keysToDelete.forEach(key => params.delete(key)); - const newQuery = params.toString(); + + // remove redundant constituent values if meta-values are present + if (options && options.metaMapping) { + ['result', 'state'].forEach(key => { + const mapping = options.metaMapping[key]; + if (!mapping) return; + const currentValues = params.getAll(key); + if (currentValues.length === 0) return; + + let newValues = [...currentValues]; + Object.keys(mapping).forEach(metaValue => { + if (currentValues.includes(metaValue)) { + const constituents = mapping[metaValue]; + newValues = newValues.filter(val => !constituents.includes(val)); + } + }); + + if (newValues.length !== currentValues.length) { + params.delete(key); + newValues.forEach(val => params.append(key, val)); + } + }); + } + + const newQuery = new URLSearchParams(Array.from(params).filter(([, value]) => value !== '')).toString(); if (newQuery !== currentQuery) { // show progress indication @@ -87,7 +106,7 @@ const updateMasterCheckbox = function (container) { const master = container.querySelector('.filter-bulk-master'); if (!master) return; - const checkboxes = container.querySelectorAll('input[type="checkbox"]:not(.filter-bulk-master)'); + const checkboxes = container.querySelectorAll('input[type="checkbox"]:not(.filter-bulk-master):not(.filter-meta)'); const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length; if (checkedCount === 0) { @@ -108,7 +127,7 @@ if (master) { const mb3 = master.closest('.mb-3'); const isChecked = master.checked; - mb3.querySelectorAll('input[type="checkbox"]:not(.filter-bulk-master)').forEach(cb => { + mb3.querySelectorAll('input[type="checkbox"]:not(.filter-bulk-master):not(.filter-meta)').forEach(cb => { cb.checked = isChecked; }); return; @@ -118,7 +137,7 @@ if (invert) { e.preventDefault(); const mb3 = invert.closest('.mb-3'); - mb3.querySelectorAll('input[type="checkbox"]:not(.filter-bulk-master)').forEach(cb => { + mb3.querySelectorAll('input[type="checkbox"]:not(.filter-bulk-master):not(.filter-meta)').forEach(cb => { cb.checked = !cb.checked; }); updateMasterCheckbox(mb3); @@ -126,13 +145,37 @@ }); container.addEventListener('change', function (e) { - if (e.target.matches('input[type="checkbox"]:not(.filter-bulk-master)')) { + if (e.target.matches('input[type="checkbox"]:not(.filter-bulk-master):not(.filter-meta)')) { updateMasterCheckbox(e.target.closest('.mb-3')); } }); updateMasterCheckbox(container); }); + + if (options && options.metaMapping) { + const filterForm = document.getElementById('filter-form'); + if (filterForm) { + ['result', 'state'].forEach(key => { + const mapping = options.metaMapping[key]; + if (!mapping) return; + Object.keys(mapping).forEach(metaValue => { + const metaCheckbox = filterForm.querySelector(`input[name="${key}"][value="${metaValue}"]`); + if (!metaCheckbox) return; + metaCheckbox.addEventListener('change', function () { + const isChecked = this.checked; + mapping[metaValue].forEach(val => { + const cb = filterForm.querySelector(`input[name="${key}"][value="${val}"]`); + if (cb) { + cb.checked = isChecked; + cb.dispatchEvent(new Event('change', {bubbles: true})); + } + }); + }); + }); + }); + } + } } function parseFilterArguments(paramHandler) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/assets/javascripts/overview.js new/openQA-5.1771589939.8f8502b4/assets/javascripts/overview.js --- old/openQA-5.1771473096.98530511/assets/javascripts/overview.js 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/assets/javascripts/overview.js 2026-02-20 13:18:59.000000000 +0100 @@ -120,7 +120,7 @@ initCollapsedParallelChildren(relatedRow, relatedTable, dependencyInfo.parents.Parallel); } -function setupOverview() { +function setupOverview(options) { setupLazyLoadingFailedSteps(); $('.timeago').timeago(); $('.cancel').bind('ajax:success', function (event, xhr, status) { @@ -198,7 +198,7 @@ ensureParallelParentsComeFirst(); collapseOkParallelChildren(); - setupFilterForm(); + setupFilterForm(options); const form = document.getElementById('filter-form'); form.todo = false; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/docs/ContainerizedSetup.asciidoc new/openQA-5.1771589939.8f8502b4/docs/ContainerizedSetup.asciidoc --- old/openQA-5.1771473096.98530511/docs/ContainerizedSetup.asciidoc 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/docs/ContainerizedSetup.asciidoc 2026-02-20 13:18:59.000000000 +0100 @@ -56,7 +56,7 @@ container (so you do not have any dependency on your host system) or to leave out the `openqa_data` container altogether (so you have only `webui` and `worker` containers and data is loaded and saved completely into your host -system). If this is what you prefer, checkout the sections +system). If this is what you prefer, check out the sections <<ContainerizedSetup.asciidoc#_keeping_all_data_in_the_data_volume_container,Keeping all data in the Data Volume Container>> and <<ContainerizedSetup.asciidoc#_keeping_all_data_on_the_host_system,Keeping all data on the host system>> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/docs/Contributing.asciidoc new/openQA-5.1771589939.8f8502b4/docs/Contributing.asciidoc --- old/openQA-5.1771473096.98530511/docs/Contributing.asciidoc 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/docs/Contributing.asciidoc 2026-02-20 13:18:59.000000000 +0100 @@ -244,14 +244,14 @@ The following explains in detail what is necessary to develop openQA code. -For a setup with containers you can checkout the +For a setup with containers you can check out the <<Contributing.asciidoc#quick-container-development-setup,Quick container development setup>> below with a complete list of instructions. Otherwise follow the detailed steps below. This setup is applicable to a container environment as well as without. -For developing openQA and os-autoinst itself it makes sense to checkout the +For developing openQA and os-autoinst itself it makes sense to check out the <<Contributing.asciidoc#repo-urls,Git repositories>> and either execute existing tests or start the daemons manually. @@ -818,12 +818,12 @@ either `./script/initdb --init_database` or `./script/upgradedb --upgrade_database`. Migrations that affect possibly big tables should be tested against a local import of -a production database to see how much time they need. Checkout the +a production database to see how much time they need. Check out the <<Contributing.asciidoc#_importing_production_data,Importing production data>> section for details. A migration can cause the analyser to regress so it produces worse query plans leading -to impaired performance. Checkout the +to impaired performance. Check out the <<Installing.asciidoc#_working_on_database_related_performance_problems,Working on database-related performance problems>> section for how to tackle this problem. @@ -915,7 +915,7 @@ [[run-tests-in-container]] === Run tests within a container The container used in this section of the documentation is not identical with the container used -within the CI. To run tests within the CI environment locally, checkout the +within the CI. To run tests within the CI environment locally, check out the <<Contributing.asciidoc#circleci-local-container,CircleCI documentation>> below. To run tests in a container please be sure that a container runtime diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/docs/GettingStarted.asciidoc new/openQA-5.1771589939.8f8502b4/docs/GettingStarted.asciidoc --- old/openQA-5.1771473096.98530511/docs/GettingStarted.asciidoc 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/docs/GettingStarted.asciidoc 2026-02-20 13:18:59.000000000 +0100 @@ -300,7 +300,7 @@ The client configuration can also be put under `~/.config/openqa/client.conf`. -For development, checkout the section about +For development, check out the section about <<Contributing.asciidoc#_customize_configuration_directory,customizing the configuration directory>>. ==== Drop-in configurations @@ -397,7 +397,7 @@ === Getting tests You can point `CASEDIR` and `NEEDLES_DIR` to Git repositories. openQA will -checkout those repositories automatically and no manual setup is needed. +check out those repositories automatically and no manual setup is needed. Otherwise you will need to clone tests and needles manually. For this, clone a subdirectory under `/var/lib/openqa/tests` for each test distribution diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/docs/Installing.asciidoc new/openQA-5.1771589939.8f8502b4/docs/Installing.asciidoc --- old/openQA-5.1771473096.98530511/docs/Installing.asciidoc 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/docs/Installing.asciidoc 2026-02-20 13:18:59.000000000 +0100 @@ -156,10 +156,10 @@ https://github.com/os-autoinst/openQA/tree/master/container/worker/conf[worker/conf] directory listings on GitHub. -To learn more about how to run workers checkout the +To learn more about how to run workers check out the <<Installing.asciidoc#_run_openqa_workers,Run openQA workers section>>. -For creating a first test job, checkout the +For creating a first test job, check out the <<UsersGuide.asciidoc#_triggering_tests,Triggering tests section>>. Note that the commands mentioned there can also be invoked within a container, e.g.: @@ -170,7 +170,7 @@ openqa-cli --help ---- -Checkout the +Check out the <<ContainerizedSetup.asciidoc#containerizedsetup,containerized setup section>> for more details. @@ -1485,7 +1485,7 @@ + IMPORTANT: Be sure to use pg_upgrade from the target version (like it is done here) and also no newer version which is possibly installed on the system as well. -Checkout the https://www.postgresql.org/docs/current/pgupgrade.html[PostgreSQL documentation] +Check out the https://www.postgresql.org/docs/current/pgupgrade.html[PostgreSQL documentation] for details. + NOTE: This step only takes a few seconds for multiple production DBs because the `--link` @@ -1531,7 +1531,7 @@ ---- == Working on database-related performance problems -Without extra setup, PostgreSQL already gathers many statistics, checkout +Without extra setup, PostgreSQL already gathers many statistics, check out https://www.postgresql.org/docs/current/monitoring-stats.html[the official documentation]. === Enable further statistics @@ -1571,15 +1571,15 @@ changes. === Further resources -* Checkout +* Check out https://www.postgresql.org/docs/current/sql-explain.html[the official documentation] for more details about `EXPLAIN`. There is also https://explain.depesz.com[service] for formatting those explanations to be more readable. -* Checkout +* Check out https://www.postgresql.org/docs/current/sql-vacuum.html[the official documentation] for more details about `VACUUM ANALYZE`. -* Checkout the following +* Check out the following https://www.postgresql.org/docs/current/performance-tips.html[documentation pages]. == Filesystem layout @@ -1664,6 +1664,9 @@ - configurable via the test variable `CASEDIR` (see backend variables documentation) - this default is provided by openQA; when starting isotovideo manually the `CASEDIR` variable *must* be initialized by hand + - relative paths are relative to `$sharedir/tests`; to avoid symlinking, + use e.g. `CASEDIR=opensuse` for `$sharedir/tests/opensuse` despite + differing `DISTRI` - might contain the sub directory `lib` for placing Perl modules used by the tests * the "product directory": contains the test schedule (`main.pm`) for a certain product within a test distribution - by default identical to the "test case directory" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/docs/UsersGuide.asciidoc new/openQA-5.1771589939.8f8502b4/docs/UsersGuide.asciidoc --- old/openQA-5.1771473096.98530511/docs/UsersGuide.asciidoc 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/docs/UsersGuide.asciidoc 2026-02-20 13:18:59.000000000 +0100 @@ -648,7 +648,7 @@ is carried over. NOTE: The lookup-depth is limited. The search for candidates will also stop -early if too many different kinds of failures were seen. Checkout the +early if too many different kinds of failures were seen. Check out the descriptions of the relevant settings in the `carry_over` section of <<GettingStarted.asciidoc#_configuration,the web UI configuration>> for details. @@ -1019,7 +1019,7 @@ The sub-commands provide further help, e.g. `openqa-cli api --help` contains a lot of examples. -This section focuses on particular API use-cases. Checkout the +This section focuses on particular API use-cases. Check out the <<Client.asciidoc#client,openQA client>> section for further information about the client itself, how authentication works and how plain `curl` can be used. @@ -1341,7 +1341,7 @@ Assets may be shared between the web-UI and the workers by having them literally use a shared file system (this used to be the only option), or by having the workers download them from the -server when needed and cache them locally. Checkout the documentation about +server when needed and cache them locally. Check out the documentation about <<Installing.asciidoc#asset-caching,asset caching>> for more on this. === Specifying assets required by a job === @@ -1450,7 +1450,7 @@ <<GettingStarted.asciidoc#_configuration,the web UI configuration>>. Many other cleanup-related settings can be found within the web UI configuration as well, e.g. the `[…_limits]` sections contain various tweaks and allow to change certain -defaults. Checkout the sub section +defaults. Check out the sub section <<UsersGuide.asciidoc#_timers_and_triggers,Timers and triggers>> to learn more about how those jobs are triggered. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/docs/WritingTests.asciidoc new/openQA-5.1771589939.8f8502b4/docs/WritingTests.asciidoc --- old/openQA-5.1771473096.98530511/docs/WritingTests.asciidoc 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/docs/WritingTests.asciidoc 2026-02-20 13:18:59.000000000 +0100 @@ -903,7 +903,7 @@ Then, in job templates, add test suite(s) and all of its dependent test suite(s). Keep in mind to place the machines which have been explicitly defined -in a variable for each dependent test suite. Checkout the following example +in a variable for each dependent test suite. Check out the following example sections to get a better understanding. ==== Handling of related jobs on failure / cancellation / restart @@ -2361,7 +2361,7 @@ It is also possible to create a GitHub workflow that will clone and monitor an openQA job which is mentioned in the PR description or comment. The scripts -repository contains a pre-defined GitHub action for this. Checkout the +repository contains a pre-defined GitHub action for this. Check out the documentation of the https://github.com/os-autoinst/os-autoinst-scripts/blob/master/openqa-clone-and-monitor-job-from-pr[openqa-clone-and-monitor-job-from-pr] script for further information and an example configuration. @@ -2391,7 +2391,7 @@ created from a fork repository branch which extra configuration. The test scenarios for your repository need to be defined in the file -`scenario-definitions.yaml` at the root of your repository. Checkout the +`scenario-definitions.yaml` at the root of your repository. Check out the https://github.com/os-autoinst/os-autoinst-distri-example/blob/master/scenario-definitions.yaml[scenario definitions] from the example distribution for an example. You may append a parameter like `SCENARIO_DEFINITIONS_YAML=path/of/yaml` to the query parameters of the webhook @@ -2401,7 +2401,7 @@ It is also possible to avoid using openQA at all and run the backend `isotovideo` directly within the CI runner. This simplifies the setup as no openQA instance is needed but of course test results cannot be examined using -a web interface as usual. Checkout the +a web interface as usual. Check out the https://github.com/os-autoinst/os-autoinst-distri-example/blob/main/README.md#local-testing-and-ci-environment[README of the example test distribution] for more information. @@ -2445,9 +2445,9 @@ 5. Keep SSL enabled. (Be sure your openQA instance is reachable via HTTPS.) 6. Select "Let me select individual events." and then "Pull requests". 7. Ensure "Active" is checked and confirm. -8. GitHub should now have been delivering a "ping" event. Checkout whether +8. GitHub should now have been delivering a "ping" event. Check out whether it could be delivered. If you have gotten a 200 response then everything - is setup correctly. Otherwise, checkout the response of the delivery to + is setup correctly. Otherwise, check out the response of the delivery to investigate what is wrong. == Integrating test results from external systems diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/CacheService.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/CacheService.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/CacheService.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/CacheService.pm 2026-02-20 13:18:59.000000000 +0100 @@ -22,7 +22,7 @@ sub _configure_sqlite_database ($self, $sqlite, $dbh) { # default to using DELETE journaling mode to avoid database corruption seen in production (see poo#67000) - # checkout https://www.sqlite.org/pragma.html#pragma_journal_mode for possible values + # check out https://www.sqlite.org/pragma.html#pragma_journal_mode for possible values my $sqlite_mode = uc($ENV{OPENQA_CACHE_SERVICE_SQLITE_JOURNAL_MODE} || 'DELETE'); $dbh->sqlite_busy_timeout(SQLITE_BUSY_TIMEOUT); $dbh->do("pragma journal_mode=$sqlite_mode"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Jobs/Constants.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Jobs/Constants.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Jobs/Constants.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Jobs/Constants.pm 2026-02-20 13:18:59.000000000 +0100 @@ -57,6 +57,7 @@ EXECUTION => 'execution', FINAL => 'final', }; +use constant META_STATES => (PRE_EXECUTION, EXECUTION, FINAL); # results for the overall job use constant { @@ -92,6 +93,24 @@ COMPLETE => 'complete', NOT_COMPLETE => 'not_complete', ABORTED => 'aborted', + OK => 'ok', + NOT_OK => 'not_ok', +}; +use constant META_RESULTS => (COMPLETE, NOT_COMPLETE, ABORTED, OK, NOT_OK); + +use constant META_MAPPING => { + state => { + PRE_EXECUTION() => [PRE_EXECUTION_STATES], + EXECUTION() => [EXECUTION_STATES], + FINAL() => [FINAL_STATES], + }, + result => { + COMPLETE() => [COMPLETE_RESULTS], + NOT_COMPLETE() => [NOT_COMPLETE_RESULTS], + ABORTED() => [ABORTED_RESULTS], + OK() => [OK_RESULTS], + NOT_OK() => [NOT_OK_RESULTS], + }, }; # results for particular job modules @@ -120,21 +139,29 @@ CANCELLED COMPLETE_RESULTS DONE + EXECUTION EXECUTION_STATES FAILED + FINAL FINAL_STATES INCOMPLETE + NOT_COMPLETE NOT_COMPLETE_RESULTS ABORTED ABORTED_RESULTS - NONE + COMPLETE + COMPLETE_RESULTS + OK + OK_RESULTS + NOT_OK NOT_OK_RESULTS + NONE OBSOLETED - OK_RESULTS PARALLEL_FAILED PARALLEL_RESTARTED PASSED PENDING_STATES + PRE_EXECUTION PRE_EXECUTION_STATES PRISTINE_STATES RESULTS @@ -148,6 +175,9 @@ USER_CANCELLED USER_RESTARTED MODULE_RESULTS + META_RESULTS + META_STATES + META_MAPPING COMMON_RESULT_FILES TIMEOUT_EXCEEDED DEFAULT_JOB_PRIORITY diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Needles.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Needles.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Needles.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Needles.pm 2026-02-20 13:18:59.000000000 +0100 @@ -28,7 +28,7 @@ sub needle_temp_dir ($dir, $ref) { path($tmp_dir, basename(dirname($dir)), $ref, 'needles') } sub _locate_needle_for_ref ($relative_needle_path, $needles_dir, $needles_ref, $needle_url) { - # checkout needle from git - return absolute path to json file on success and undef on error + # check out needle from git - return absolute path to json file on success and undef on error return undef unless defined $needles_ref; my $app = OpenQA::App->singleton; my $allow_arbitrary_url_fetch = $app->config->{'scm git'}->{allow_arbitrary_url_fetch} eq 'yes'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Scheduler/Model/Jobs.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Scheduler/Model/Jobs.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Scheduler/Model/Jobs.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Scheduler/Model/Jobs.pm 2026-02-20 13:18:59.000000000 +0100 @@ -28,8 +28,14 @@ has shuffle_workers => 1; sub determine_free_workers ($shuffle = 0) { - my @free_workers = grep { !$_->dead && ($_->websocket_api_version() || 0) == WEBSOCKET_API_VERSION } - OpenQA::Schema->singleton->resultset('Workers')->search({job_id => undef, error => undef})->all; + my @free_workers = grep { !$_->dead } OpenQA::Schema->singleton->resultset('Workers')->search( + { + job_id => undef, + error => undef, + 'properties.key' => 'WEBSOCKET_API_VERSION', + 'properties.value' => WEBSOCKET_API_VERSION + }, + {join => 'properties'})->all; return $shuffle ? shuffle(\@free_workers) : \@free_workers; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Schema/Result/Jobs.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Schema/Result/Jobs.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Schema/Result/Jobs.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Schema/Result/Jobs.pm 2026-02-20 13:18:59.000000000 +0100 @@ -784,7 +784,7 @@ next if exists $jobs->{$parent_id}; die "Direct parent $parent_id needs to be cloned as well for the directly chained dependency " . 'to work correctly. It is generally better to restart the parent which will restart all children ' - . 'as well. Checkout the ' + . 'as well. Check out the ' . '<a href="https://open.qa/docs/#_handling_of_related_jobs_on_failure_cancellation_restart">' . "documentation</a> for more information.\n" if $no_directly_chained_parent; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Schema/Result/Workers.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Schema/Result/Workers.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Schema/Result/Workers.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Schema/Result/Workers.pm 2026-02-20 13:18:59.000000000 +0100 @@ -127,8 +127,9 @@ } sub websocket_api_version ($self) { - return $self->{_websocket_api_version} if exists $self->{_websocket_api_version}; - return $self->{_websocket_api_version} = $self->get_property('WEBSOCKET_API_VERSION'); + my $v = $self->{_websocket_api_version} // $self->get_property('WEBSOCKET_API_VERSION'); + return $self->{_websocket_api_version} = $v if $v; + return undef; } sub check_class { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Script/CloneJob.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Script/CloneJob.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Script/CloneJob.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Script/CloneJob.pm 2026-02-20 13:18:59.000000000 +0100 @@ -215,7 +215,7 @@ api => $local_url->host, apikey => $options->{apikey}, apisecret => $options->{apisecret}); - die "API key/secret for '$options->{host}' missing. Checkout '$0 --help' for the config file syntax/lookup.\n" + die "API key/secret for '$options->{host}' missing. Check out '$0 --help' for the config file syntax/lookup.\n" if !$options->{'export-command'} && !($local->apikey && $local->apisecret); # configure user agents for the source host (usually a remote host) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Shared/Plugin/Gru.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Shared/Plugin/Gru.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Shared/Plugin/Gru.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Shared/Plugin/Gru.pm 2026-02-20 13:18:59.000000000 +0100 @@ -321,7 +321,7 @@ return Mojo::Promise->reject($result, 500) if ref $result eq 'HASH' && $result->{error}; # format error message (fallback for general case) - my $msg = ref $result eq '' && $result ? $result : 'Checkout Minion dashboard for further details.'; + my $msg = ref $result eq '' && $result ? $result : 'check out Minion dashboard for further details.'; return Mojo::Promise->reject({error => "Task for $task_description failed: $msg", result => $result}, 500); }); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Task/Git/Clone.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Task/Git/Clone.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Task/Git/Clone.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Task/Git/Clone.pm 2026-02-20 13:18:59.000000000 +0100 @@ -121,7 +121,7 @@ die <<~"END_OF_MESSAGE" unless $git->is_workdir_clean; NOT updating dirty Git checkout at '$path'. In case this is expected (e.g. on a development openQA instance) you can disable auto-updating. - Then the Git checkout will no longer be kept up-to-date, though. Checkout http://open.qa/docs/#_getting_tests for details. + Then the Git checkout will no longer be kept up-to-date, though. Check out http://open.qa/docs/#_getting_tests for details. END_OF_MESSAGE unless ($requested_branch) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Utils.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Utils.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Utils.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Utils.pm 2026-02-20 13:18:59.000000000 +0100 @@ -193,9 +193,9 @@ # To be backwards compatible we need to search for all combinations of "old/new # testrepository name" and "old/new folder structure" within the # testrepository. -sub productdir ($distri = undef, $version = undef, $rootfortests = undef) { - my $dir = testcasedir($distri, $version, $rootfortests); - return "$dir/products/$distri" if $distri && -e "$dir/products/$distri"; +sub productdir ($effective_distri = undef, $version = undef, $rootfortests = undef, $distri = undef) { + my $dir = testcasedir($effective_distri, $version, $rootfortests); + return "$dir/products/$distri" if ($distri //= $effective_distri) && -e "$dir/products/$distri"; return $dir; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/WebAPI/Controller/Test.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/WebAPI/Controller/Test.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/WebAPI/Controller/Test.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/WebAPI/Controller/Test.pm 2026-02-20 13:18:59.000000000 +0100 @@ -918,6 +918,7 @@ summary_category_query => \%SUMMARY_CATEGORY_QUERY, only_distri => $only_distri, limit_exceeded => $exceeded_limit, + meta_mapping => META_MAPPING, ); $self->stash(\%stash); $self->respond_to(json => {json => \%stash}, html => {template => 'test/overview'}); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/WebAPI/Plugin/Helpers.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/WebAPI/Plugin/Helpers.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/WebAPI/Plugin/Helpers.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/WebAPI/Plugin/Helpers.pm 2026-02-20 13:18:59.000000000 +0100 @@ -9,7 +9,13 @@ use OpenQA::Schema; use OpenQA::Utils qw(bugurl human_readable_size render_escaped_refs href_to_bugref); use OpenQA::Events; -use OpenQA::Jobs::Constants qw(EXECUTION_STATES PRE_EXECUTION_STATES ABORTED_RESULTS FAILED NOT_COMPLETE_RESULTS); +use OpenQA::Jobs::Constants qw( + EXECUTION_STATES PRE_EXECUTION_STATES ABORTED_RESULTS FAILED NOT_COMPLETE_RESULTS + COMPLETE NOT_COMPLETE ABORTED COMPLETE_RESULTS + OK NOT_OK OK_RESULTS NOT_OK_RESULTS + PRE_EXECUTION EXECUTION FINAL FINAL_STATES + META_RESULTS META_STATES META_MAPPING +); use Text::Glob qw(glob_to_regex_string); use List::Util qw(any min); use Feature::Compat::Try; @@ -518,6 +524,16 @@ my $results = $c->every_non_empty_param('result'); my $states_not = $c->every_non_empty_param('state__not'); my $results_not = $c->every_non_empty_param('result__not'); + + # expansion of meta-values + my $expand = sub ($params, $meta) { + [map { $meta->{$_} ? @{$meta->{$_}} : $_ } @$params] + }; + $states = $expand->($states, META_MAPPING->{state}); + $results = $expand->($results, META_MAPPING->{result}); + $states_not = $expand->($states_not, META_MAPPING->{state}); + $results_not = $expand->($results_not, META_MAPPING->{result}); + my $archs = $c->every_non_empty_param('arch'); my $machines = $c->every_non_empty_param('machine'); my $failed_modules = $c->every_non_empty_param('failed_modules'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/lib/OpenQA/Worker/Engines/isotovideo.pm new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Worker/Engines/isotovideo.pm --- old/openQA-5.1771473096.98530511/lib/OpenQA/Worker/Engines/isotovideo.pm 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/lib/OpenQA/Worker/Engines/isotovideo.pm 2026-02-20 13:18:59.000000000 +0100 @@ -394,7 +394,7 @@ my $effective_distri = defined $casedir && !looks_like_url_with_scheme($casedir) ? $casedir : $vars->{DISTRI}; my @vars_for_default_dirs = ($effective_distri, $vars->{VERSION}, $shared_cache); my $default_casedir = testcasedir(@vars_for_default_dirs); - my $default_productdir = productdir(@vars_for_default_dirs); + my $default_productdir = productdir(@vars_for_default_dirs, $vars->{DISTRI}); my $target_name = path($default_casedir)->basename; my $has_custom_dir = $casedir || $vars->{PRODUCTDIR}; $casedir = $vars->{CASEDIR} //= $absolute_paths ? $default_casedir : $target_name; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/script/openqa-auto-update new/openQA-5.1771589939.8f8502b4/script/openqa-auto-update --- old/openQA-5.1771473096.98530511/script/openqa-auto-update 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/script/openqa-auto-update 2026-02-20 13:18:59.000000000 +0100 @@ -23,7 +23,7 @@ to be considered (default: $min_mtime) OPENQA_PACKAGE_CACHE_REPO_GLOB: the glob to find relevant packages (default: $glob) -Checkout "openqa-clean-repo-cache --help" for details. +Check out "openqa-clean-repo-cache --help" for details. Options: -h, --help display this help diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/script/openqa-bootstrap new/openQA-5.1771589939.8f8502b4/script/openqa-bootstrap --- old/openQA-5.1771473096.98530511/script/openqa-bootstrap 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/script/openqa-bootstrap 2026-02-20 13:18:59.000000000 +0100 @@ -149,9 +149,6 @@ if [[ ! -e /var/lib/openqa/tests/sle ]]; then ln -s opensuse /var/lib/openqa/tests/sle fi - if [[ ! -e /var/lib/openqa/tests/aeon ]]; then - ln -s opensuse /var/lib/openqa/tests/aeon - fi if ping -c1 gitlab.suse.de.; then sles_needles_giturl="https://gitlab.suse.de/openqa/os-autoinst-needles-sles.git" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/10-tests_overview.t new/openQA-5.1771589939.8f8502b4/t/10-tests_overview.t --- old/openQA-5.1771473096.98530511/t/10-tests_overview.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/10-tests_overview.t 2026-02-20 13:18:59.000000000 +0100 @@ -467,6 +467,19 @@ like($summary, qr/Scheduled: 2/i, 'Scheduled jobs remain'); }; +subtest 'Meta-filters' => sub { + $t->get_ok('/tests/overview?distri=opensuse&version=13.1&build=0091&result=complete')->status_is(200); + my $summary = get_summary; + like($summary, qr/Passed: 3/i, 'Passed jobs are included via "complete" meta-result'); + $t->get_ok('/tests/overview?distri=opensuse&version=13.1&build=0091&state=final')->status_is(200); + $summary = get_summary; + like($summary, qr/Passed: 3/i, 'Done jobs are included via "final" meta-state'); + $t->get_ok('/tests/overview?distri=opensuse&version=13.1&build=0091&result__not=complete')->status_is(200); + $summary = get_summary; + unlike($summary, qr/Passed: [1-9]/i, 'Passed jobs are excluded via NOT "complete"'); + like($summary, qr/Scheduled: 2 Running: 2/i, 'Other categories remain'); +}; + subtest 'Maximum jobs limit' => sub { $t->get_ok('/tests/overview')->status_is(200)->element_exists_not('#max-jobs-limit') ->element_count_is('table.overview td.name', 7); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/43-scheduling-and-worker-scalability.t new/openQA-5.1771589939.8f8502b4/t/43-scheduling-and-worker-scalability.t --- old/openQA-5.1771473096.98530511/t/43-scheduling-and-worker-scalability.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/43-scheduling-and-worker-scalability.t 2026-02-20 13:18:59.000000000 +0100 @@ -148,87 +148,61 @@ my $polling_tries_jobs = $seconds_to_wait_per_job / $polling_interval * $job_count; subtest 'wait for workers to be idle' => sub { - # wait for all workers to register - my @worker_search_args = ({'properties.key' => 'WEBSOCKET_API_VERSION'}, {join => 'properties'}); + # wait for all workers to register, have correct API version and be idle + # this ensures they are fully visible to the scheduler my $actual_count = 0; for my $try (1 .. $polling_tries_workers) { - last if ($actual_count = $workers->search(@worker_search_args)->count) == $worker_count; - note("Waiting until all workers are registered, try $try"); # uncoverable statement + my @idle + = grep { $_->status eq 'idle' && ($_->websocket_api_version || 0) == WEBSOCKET_API_VERSION } $workers->all; + $actual_count = scalar @idle; + last if $actual_count == $worker_count; + note("Waiting until all workers are registered and idle, try $try"); # uncoverable statement sleep $polling_interval; # uncoverable statement } - is $actual_count, $worker_count, 'all workers registered'; - - # wait for expected number of workers to become limited - my $limited_workers = max(0, $worker_count - $worker_limit); - $worker_count = min($worker_count, $worker_limit); - for my $try (1 .. $polling_tries_workers) { - last if ($actual_count = $workers->search({error => {-like => '%limited%'}})->count) == $limited_workers; - note("Waiting until $limited_workers workers are limited, try $try"); # uncoverable statement - sleep $polling_interval; # uncoverable statement - } - is $actual_count, $limited_workers, 'expected number of workers limited'; + is $actual_count, $worker_count, 'all workers registered and idle'; # check that no workers are in unexpected offline/error states my @non_idle_workers; for my $worker ($workers->all) { my $is_idle = $worker->status eq 'idle'; - my $is_idle_or_limited = $is_idle || $worker->error =~ qr/limited/; $worker_ids{$worker->id} = 1 if $is_idle; push(@non_idle_workers, $worker->info) # uncoverable statement - if !$is_idle_or_limited || ($worker->websocket_api_version || 0) != WEBSOCKET_API_VERSION; + if !$is_idle || ($worker->websocket_api_version || 0) != WEBSOCKET_API_VERSION; } - is scalar @non_idle_workers, 0, 'all workers idling/limited' or always_explain \@non_idle_workers; + is scalar @non_idle_workers, 0, 'all workers idling' or always_explain \@non_idle_workers; }; subtest 'assign and run jobs' => sub { my $scheduler = OpenQA::Scheduler::Model::Jobs->singleton; - my $allocated = $scheduler->schedule; - unless (ref($allocated) eq 'ARRAY' && @$allocated > 0) { - always_explain 'Allocated: ', $allocated; # uncoverable statement - always_explain 'Scheduled: ', $scheduler->scheduled_jobs; # uncoverable statement - BAIL_OUT('Unable to assign jobs to (idling) workers'); # uncoverable statement + + # ensure the scheduler also sees them as free + for my $try (1 .. $polling_tries_workers) { + last if scalar @{OpenQA::Scheduler::Model::Jobs::determine_free_workers()} == $worker_count; + sleep $polling_interval; # uncoverable statement + } + + # retry scheduling until all workers have a job assigned (or all jobs are assigned) + my $expected_allocated = min($worker_count, $job_count); + for my $try (1 .. $polling_tries_workers) { + $scheduler->schedule; + last if $jobs->search({state => {-in => [ASSIGNED, SETUP, RUNNING, DONE]}})->count >= $expected_allocated; + sleep $polling_interval; # uncoverable statement } + my $allocated_count = $jobs->search({state => {-in => [ASSIGNED, SETUP, RUNNING, DONE]}})->count; + ok($allocated_count >= $expected_allocated, 'all workers have a job or all jobs assigned') + or diag("Allocated count: $allocated_count, expected at least: $expected_allocated"); + my $remaining_jobs = $job_count - $worker_count; - note('Assigned jobs: ' . dumper($allocated)); note('Remaining ' . ($remaining_jobs > 0 ? ('jobs: ' . $remaining_jobs) : ('workers: ' . -$remaining_jobs))); - if ($remaining_jobs > 0) { - is(scalar @$allocated, $worker_count, 'each worker has a job assigned'); - } - elsif ($remaining_jobs < 0) { - # uncoverable statement only executed when there are more jobs than workers based config parameters - is(scalar @$allocated, $job_count, 'each job has a worker assigned'); - } - else { - # uncoverable statement only executed when the number of workers and jobs are equal based on config parameters - is(scalar @$allocated, $job_count, 'all jobs assigned and all workers busy'); - # uncoverable statement count:1 - # uncoverable statement count:2 - my @allocated_job_ids = map { $_->{job} } @$allocated; - # uncoverable statement count:1 - # uncoverable statement count:2 - my @allocated_worker_ids = map { $_->{worker} } @$allocated; - # uncoverable statement count:1 - # uncoverable statement count:2 - my @expected_job_ids = map { int($_) } keys %job_ids; - # uncoverable statement count:1 - # uncoverable statement count:2 - my @expected_worker_ids = map { int($_) } keys %worker_ids; - # uncoverable statement - is_deeply([sort @allocated_job_ids], [sort @expected_job_ids], 'all jobs allocated'); - # uncoverable statement - is_deeply([sort @allocated_worker_ids], [sort @expected_worker_ids], 'all workers allocated'); - } + for my $try (1 .. $polling_tries_jobs) { my $done_count = $jobs->search({state => DONE})->count; last if $done_count == $job_count; my $scheduled_count = $jobs->search({state => SCHEDULED})->count; if ($scheduled_count > 0) { note("Trying to assign $scheduled_count scheduled jobs"); - if (my $allocated = OpenQA::Scheduler::Model::Jobs->singleton->schedule) { - my $assigned_job_count = scalar @$allocated; - note("Assigned $assigned_job_count more jobs: " . dumper($allocated)) if $assigned_job_count > 0; - } + OpenQA::Scheduler::Model::Jobs->singleton->schedule; } note("Waiting until all jobs are done ($done_count/$job_count), try $try"); sleep $polling_interval; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/45-make-update-deps.t new/openQA-5.1771589939.8f8502b4/t/45-make-update-deps.t --- old/openQA-5.1771473096.98530511/t/45-make-update-deps.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/45-make-update-deps.t 2026-02-20 13:18:59.000000000 +0100 @@ -6,6 +6,8 @@ use Test::Warnings; # no OpenQA::Test::TimeLimit for this trivial test +plan skip_all => 'SKIP_UPDATE_DEPS is set' if $ENV{SKIP_UPDATE_DEPS}; + my $make = 'make update-deps'; my @out = qx{$make}; my $rc = $?; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/ui/10-tests_overview.t new/openQA-5.1771589939.8f8502b4/t/ui/10-tests_overview.t --- old/openQA-5.1771473096.98530511/t/ui/10-tests_overview.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/ui/10-tests_overview.t 2026-02-20 13:18:59.000000000 +0100 @@ -696,18 +696,20 @@ my $master = $driver->find_child_element($results_div, '.filter-bulk-master'); subtest 'select all' => sub { $master->click(); - my @checkboxes = $driver->find_child_elements($results_div, 'input[type="checkbox"]:not(.filter-bulk-master)'); + my @checkboxes = $driver->find_child_elements($results_div, + 'input[type="checkbox"]:not(.filter-bulk-master):not(.filter-meta)'); my $all_selected = 1; for my $cb (@checkboxes) { $all_selected = 0 unless $cb->is_selected; } - ok $all_selected, 'all checkboxes selected after master checkbox click'; + ok $all_selected, 'all base checkboxes selected after master checkbox click'; ok $master->is_selected, 'master checkbox is checked'; }; subtest 'select none' => sub { $master->click(); - my @checkboxes = $driver->find_child_elements($results_div, 'input[type="checkbox"]:not(.filter-bulk-master)'); + my @checkboxes = $driver->find_child_elements($results_div, + 'input[type="checkbox"]:not(.filter-bulk-master):not(.filter-meta)'); my $none_selected = 1; for my $cb (@checkboxes) { $none_selected = 0 if $cb->is_selected; } - ok $none_selected, 'all checkboxes deselected after master checkbox click again'; + ok $none_selected, 'all base checkboxes deselected after master checkbox click again'; ok !$master->is_selected, 'master checkbox is unchecked'; }; subtest 'partially selected' => sub { @@ -792,6 +794,79 @@ unlike $url, qr/machine=/, 'URL does not contain empty machine parameter'; }; +subtest 'meta-filter checkboxes' => sub { + $driver->get('/tests/overview'); + $driver->find_element('#filter-panel .card-header')->click(); + wait_for_element(selector => '#filter-complete', is_displayed => 1); + ok $driver->find_element_by_id('filter-complete'), 'complete meta-result checkbox present'; + ok $driver->find_element_by_id('filter-final'), 'final meta-state checkbox present'; + $driver->find_element_by_id('filter-complete')->click(); + $driver->find_element('#filter-form button[type="submit"]')->click(); + wait_until sub { $driver->get_current_url =~ qr/result=complete/ }, 'form submitted with meta-result'; + like $driver->get_current_url, qr/result=complete/, 'URL contains meta-result parameter'; +}; + +subtest 'meta-checkbox updates individual checkboxes' => sub { + $driver->get('/tests/overview'); + $driver->find_element('#filter-panel .card-header')->click(); + wait_for_element(selector => '#filter-ok', is_displayed => 1); + my $ok_cb = $driver->find_element_by_id('filter-ok'); + my $passed_cb = $driver->find_element_by_id('filter-passed'); + my $softfailed_cb = $driver->find_element_by_id('filter-softfailed'); + ok !$passed_cb->is_selected, 'passed checkbox initially unchecked'; + ok !$softfailed_cb->is_selected, 'softfailed checkbox initially unchecked'; + $ok_cb->click(); + ok $passed_cb->is_selected, 'passed checkbox checked after meta-ok click'; + ok $softfailed_cb->is_selected, 'softfailed checkbox checked after meta-ok click'; + $ok_cb->click(); + ok !$passed_cb->is_selected, 'passed checkbox unchecked after meta-ok click again'; + ok !$softfailed_cb->is_selected, 'softfailed checkbox unchecked after meta-ok click again'; +}; + +subtest 'redundant parameters are removed' => sub { + $driver->get('/tests/overview'); + $driver->find_element('#filter-panel .card-header')->click(); + wait_for_element(selector => '#filter-ok', is_displayed => 1); + $driver->find_element_by_id('filter-ok')->click(); + my $passed_cb = $driver->find_element_by_id('filter-passed'); + ok $passed_cb->is_selected, 'passed selected by meta-ok'; + $passed_cb->click(); + $passed_cb->click(); + $driver->find_element('#filter-form button[type="submit"]')->click(); + wait_until sub { $driver->get_current_url =~ qr/result=ok/ }, 'form submitted'; + my $url = $driver->get_current_url; + like $url, qr/result=ok/, 'URL contains result=ok'; + unlike $url, qr/result=passed/, 'URL does not contain redundant result=passed'; +}; + +subtest 'all but one results in negative filter' => sub { + $driver->get('/tests/overview'); + $driver->find_element('#filter-panel .card-header')->click(); + wait_for_element(selector => '#filter-results', is_displayed => 1); + my $results_div = $driver->find_element('#filter-results'); + $driver->find_child_element($results_div, '.filter-bulk-master')->click(); + $driver->find_element_by_id('filter-passed')->click(); + $driver->find_element('#filter-form button[type="submit"]')->click(); + wait_until sub { $driver->get_current_url =~ qr/result__not=passed/ }, 'form submitted'; + like $driver->get_current_url, qr/result__not=passed/, 'URL contains result__not=passed'; +}; + +subtest 'meta-checkboxes are ignored by bulk operations' => sub { + $driver->get('/tests/overview'); + $driver->find_element('#filter-panel .card-header')->click(); + wait_for_element(selector => '#filter-ok', is_displayed => 1); + my $results_div = $driver->find_element('#filter-results'); + my $master = $driver->find_child_element($results_div, '.filter-bulk-master'); + my $ok_cb = $driver->find_element_by_id('filter-ok'); + ok !$ok_cb->is_selected, 'meta-ok initially unchecked'; + $master->click(); + ok $driver->find_element_by_id('filter-passed')->is_selected, 'passed selected by master'; + ok !$ok_cb->is_selected, 'meta-ok NOT selected by master'; + $driver->find_child_element($results_div, '.filter-bulk-invert')->click(); + ok !$driver->find_element_by_id('filter-passed')->is_selected, 'passed deselected by invert'; + ok !$ok_cb->is_selected, 'meta-ok still NOT selected by invert'; +}; + kill_driver(); done_testing(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/ui/18-tests-details.t new/openQA-5.1771589939.8f8502b4/t/ui/18-tests-details.t --- old/openQA-5.1771473096.98530511/t/ui/18-tests-details.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/ui/18-tests-details.t 2026-02-20 13:18:59.000000000 +0100 @@ -545,15 +545,15 @@ like $driver->find_element('.embedded-logfile')->get_text, qr{/usr/bin/qemu-kvm}, 'qemu-kvm is shown in log viewer'; $driver->find_element('#filter-log-file')->send_keys('kate'); - like $driver->find_element('#filter-info')->get_text, qr{Showing 3 / 1292 lines}, - 'Showing filter result info for substring'; + wait_until(sub { $driver->find_element('#filter-info')->get_text =~ qr{Showing 3 / 1292 lines} }, + 'Showing filter result info for substring'); unlike $driver->find_element('.embedded-logfile')->get_text, qr{/usr/bin/qemu-kvm}, 'qemu-kvm is not shown when filtering for something else'; $driver->find_element('#filter-log-file')->clear; - like $driver->find_element('#filter-info')->get_text, qr{^$}, 'Filter result info cleared'; + wait_until(sub { $driver->find_element('#filter-info')->get_text eq '' }, 'Filter result info cleared'); $driver->find_element('#filter-log-file')->send_keys('/kate-[12]/'); - like $driver->find_element('#filter-info')->get_text, qr{Showing 2 / 1292 lines}, - 'Showing filter result info for regex'; + wait_until(sub { $driver->find_element('#filter-info')->get_text =~ qr{Showing 2 / 1292 lines} }, + 'Showing filter result info for regex'); my $url = Mojo::URL->new($driver->execute_script('return document.location.toString()')); is $url->query->param('filename'), 'autoinst-log.txt', 'URL still contains filename after filtering'; @@ -770,8 +770,13 @@ selector => '#needlediff_selector .show-needle-info', is_displayed => 1, trigger_function => sub () { - # open the candidates menu at the beginning and try again before every second check - return if $clicks != 0 && ($clicks % 2) == 1; + # open the candidates menu at the beginning and try clicking again after 50 checks (so after around 5 s) + # note: Opening the menu is retried because it is not working reliably (see poo#164745). Trying to open + # after every 2nd attempt (as introduced in a134be8) is also not reliable as the 2nd click might + # accidentally close the menu again if it shows up after all (see poo#196829). So we now give the + # menu around 5 seconds to show up. If it hasn't shown up then, it is most likely also not going to + # show up anymore and we can try clicking again. + return if $clicks != 0 && ($clicks % 50) == 0; wait_for_element(selector => '#candidatesMenu', is_displayed => 1)->click; ++$clicks; })->click; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/ui/21-admin-needles.t new/openQA-5.1771589939.8f8502b4/t/ui/21-admin-needles.t --- old/openQA-5.1771473096.98530511/t/ui/21-admin-needles.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/ui/21-admin-needles.t 2026-02-20 13:18:59.000000000 +0100 @@ -134,7 +134,10 @@ wait_until( sub { @outstanding_needles = $driver->find_elements('#outstanding-needles li', 'css'); - return scalar @outstanding_needles == 2; + return + scalar @outstanding_needles == 2 + && $outstanding_needles[0]->get_text() eq 'inst-timezone-text.json' + && $outstanding_needles[1]->get_text() eq 'never-matched.json'; }, 'still two needle outstanding for deletion' ); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/ui/26-jobs_restart.t new/openQA-5.1771589939.8f8502b4/t/ui/26-jobs_restart.t --- old/openQA-5.1771473096.98530511/t/ui/26-jobs_restart.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/ui/26-jobs_restart.t 2026-02-20 13:18:59.000000000 +0100 @@ -102,8 +102,7 @@ $driver->find_element('#restart-result-skip-ok-children') ->attribute_like('href', qr/skip_ok_result_children=1/, 'skip OK children API URL correct'); $driver->find_element('#restart-result-skip-children')->click; - wait_for_ajax(msg => 'wait for redirection to clone job'); - like $driver->get_current_url, qr{/tests/99982}, 'shows cloned job'; + wait_until(sub { $driver->get_current_url =~ qr{/tests/99982} }, 'shows cloned job'); $jobs->find(99982)->delete; }; subtest 'child job shows options for advanced restart' => sub { @@ -159,9 +158,8 @@ subtest 'successful restart' => sub { is($driver->get('/tests/99946'), 1, 'go to job 99946'); update_last_job_id; - $driver->find_element('#restart-result')->click(); - wait_for_ajax(msg => 'successful restart from info panel in test results'); - like($driver->get_current_url(), expected_job_id_regex, 'auto refresh to restarted job'); + $driver->find_element('#restart-result')->click; + wait_until(sub { $driver->get_current_url =~ expected_job_id_regex }, 'auto refresh to restarted job'); like($driver->find_element('#info_box .card-header')->get_text(), qr/textmode\@32bit/, 'restarted job is correct'); }; @@ -182,8 +180,7 @@ ); is($td->get_text(), 'kde@64bit-uefi', 'job not marked as restarted'); $driver->find_element('#flash-messages-finished-jobs button.force-restart')->click(); - wait_for_ajax(msg => 'forced job restart'); - is($td->get_text(), 'kde@64bit-uefi (restarted)', 'job is marked as restarted'); + wait_until(sub { $td->get_text() =~ qr/kde\@64bit-uefi \(restarted\)/ }, 'job is marked as restarted'); $_->click for $driver->find_elements('#flash-messages-finished-jobs button.close'); }; @@ -192,8 +189,7 @@ my $td = $driver->find_element('#job_99926 td.test'); is($td->get_text(), 'minimalx@32bit', '99926 is minimalx@32bit'); $driver->find_child_element($td, '.restart', 'css')->click(); - wait_for_ajax(msg => 'successful job restart'); - is($td->get_text(), 'minimalx@32bit (restarted)', 'job is marked as restarted'); + wait_until(sub { $td->get_text() =~ qr/minimalx\@32bit \(restarted\)/ }, 'job is marked as restarted'); like($driver->find_child_element($td, "./a[\@title='new test']", 'xpath')->get_attribute('href'), expected_job_id_regex, 'restart link is correct'); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/t/ui/27-plugin_obs_rsync_status_details.t new/openQA-5.1771589939.8f8502b4/t/ui/27-plugin_obs_rsync_status_details.t --- old/openQA-5.1771473096.98530511/t/ui/27-plugin_obs_rsync_status_details.t 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/t/ui/27-plugin_obs_rsync_status_details.t 2026-02-20 13:18:59.000000000 +0100 @@ -64,18 +64,23 @@ if ($proj eq 'Batch1') { # click on the various buttons within the table $driver->find_element("tr#folder_$ident .obsbuildsupdate")->click; - is $driver->find_element("tr#folder_$ident .obsbuilds")->get_text, 'fake response', - 'builds update response shown'; + wait_until(sub { $driver->find_element("tr#folder_$ident .obsbuilds")->get_text eq 'fake response' }, + 'builds update response shown'); $driver->find_element("tr#folder_$ident .lastsyncforget")->click; $driver->accept_alert; - is $driver->find_element("tr#folder_$ident .lastsync")->get_text, 'fake response', 'forget response shown'; + wait_until(sub { $driver->find_element("tr#folder_$ident .lastsync")->get_text eq 'fake response' }, + 'forget response shown'); $driver->find_element("tr#folder_$ident .dirtystatusupdate")->click; - is $driver->find_element("tr#folder_$ident .dirtystatuscol .dirtystatus")->get_text, 'fake response', - 'dirty status update response shown'; + wait_until( + sub { + my $text = $driver->find_element("tr#folder_$ident .dirtystatuscol .dirtystatus")->get_text; + return $text eq 'fake response' || $text =~ qr/dirty on \d{4}-\d{2}-\d{2}/; + }, + 'dirty status update response shown' + ); - my $actual_requests = $driver->execute_script('return window.ajaxRequests;'); my @expected_requests = ( {method => 'POST', url => '/admin/obs_rsync/BatchedProj%7CBatch1/obs_builds_text'}, {url => '/admin/obs_rsync/BatchedProj%7CBatch1/obs_builds_text'}, @@ -84,6 +89,9 @@ {method => 'POST', url => '/admin/obs_rsync/BatchedProj/dirty_status'}, {url => '/admin/obs_rsync/BatchedProj/dirty_status'}, ); + wait_until(sub { scalar @{$driver->execute_script('return window.ajaxRequests;')} == @expected_requests }, + 'all ajax requests recorded'); + my $actual_requests = $driver->execute_script('return window.ajaxRequests;'); is_deeply $actual_requests, \@expected_requests, 'ajax requests done as expected' or always_explain $actual_requests; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/templates/webapi/admin/job_template/index.html.ep new/openQA-5.1771589939.8f8502b4/templates/webapi/admin/job_template/index.html.ep --- old/openQA-5.1771473096.98530511/templates/webapi/admin/job_template/index.html.ep 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/templates/webapi/admin/job_template/index.html.ep 2026-02-20 13:18:59.000000000 +0100 @@ -70,7 +70,7 @@ <h3 id="job-config-templates-heading" style="display: none;">Job templates</h3> <p> A job template is a combination of machine, product and test suite settings. These combination are used - to produce actual jobs within a certain job group. Checkout + to produce actual jobs within a certain job group. Check out <a href="http://open.qa/docs/#_using_job_templates_to_automate_jobs_creation" target="blank">the documentation</a> for more details. </p> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/templates/webapi/test/live.html.ep new/openQA-5.1771589939.8f8502b4/templates/webapi/test/live.html.ep --- old/openQA-5.1771473096.98530511/templates/webapi/test/live.html.ep 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/templates/webapi/test/live.html.ep 2026-02-20 13:18:59.000000000 +0100 @@ -65,7 +65,7 @@ <span class="developer-mode-element" data-visible-on="ownSession"> Change the test behaviour with the controls below. </span> - For more information, checkout the <a href="https://github.com/os-autoinst/openQA/blob/master/docs/UsersGuide.asciidoc#developer-mode" target="blank">documentation</a>. + For more information, check out the <a href="https://github.com/os-autoinst/openQA/blob/master/docs/UsersGuide.asciidoc#developer-mode" target="blank">documentation</a>. </p> <div class="mb-3 row"> <div class="col-sm-5"> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/templates/webapi/test/overview.html.ep new/openQA-5.1771589939.8f8502b4/templates/webapi/test/overview.html.ep --- old/openQA-5.1771473096.98530511/templates/webapi/test/overview.html.ep 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/templates/webapi/test/overview.html.ep 2026-02-20 13:18:59.000000000 +0100 @@ -6,7 +6,7 @@ % content_for 'ready_function' => begin window.overviewParallelChildrenCollapsableResultsSel = '<%= $parallel_children_collapsable_results_sel %>'; - setupOverview(); + setupOverview({metaMapping: <%== Mojo::JSON::to_json($meta_mapping) %>}); % end % if ($limit_exceeded) { @@ -96,6 +96,10 @@ % for my $result (OpenQA::Jobs::Constants::RESULTS) { <label class="form-label"><input value="<%= $result %>" name="result" type="checkbox" id="filter-<%= $result %>"> <%= ucfirst $result =~ s/_/ /r %></label> % } + <hr class="my-1"> + % for my $result (OpenQA::Jobs::Constants::META_RESULTS) { + <label class="form-label" title="Meta-category: <%= $result %>"><input value="<%= $result %>" name="result" type="checkbox" id="filter-<%= $result %>" class="filter-meta"> <strong><%= ucfirst $result =~ s/_/ /r %></strong></label> + % } </div> <div class="mb-3" id="filter-states"> <div class="d-flex align-items-center mb-1"> @@ -108,6 +112,10 @@ % for my $state (OpenQA::Jobs::Constants::STATES) { <label class="form-label"><input value="<%= $state %>" name="state" type="checkbox" id="filter-<%= $state %>"> <%= ucfirst $state =~ s/_/ /r %></label> % } + <hr class="my-1"> + % for my $state (OpenQA::Jobs::Constants::META_STATES) { + <label class="form-label" title="Meta-category: <%= $state %>"><input value="<%= $state %>" name="state" type="checkbox" id="filter-<%= $state %>" class="filter-meta"> <strong><%= ucfirst $state =~ s/_/ /r %></strong></label> + % } </div> <div class="row" id="filter-arch-flavor"> <div class="col-5"> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openQA-5.1771473096.98530511/tools/unstable_tests.txt new/openQA-5.1771589939.8f8502b4/tools/unstable_tests.txt --- old/openQA-5.1771473096.98530511/tools/unstable_tests.txt 2026-02-19 04:51:36.000000000 +0100 +++ new/openQA-5.1771589939.8f8502b4/tools/unstable_tests.txt 2026-02-20 13:18:59.000000000 +0100 @@ -1,4 +1,3 @@ t/25-cache-service.t -t/43-scheduling-and-worker-scalability.t t/ui/26-jobs_restart.t t/ui/13-admin.t ++++++ openQA.obsinfo ++++++ --- /var/tmp/diff_new_pack.9RcvFQ/_old 2026-02-21 21:04:53.640108751 +0100 +++ /var/tmp/diff_new_pack.9RcvFQ/_new 2026-02-21 21:04:53.672110069 +0100 @@ -1,5 +1,5 @@ name: openQA -version: 5.1771473096.98530511 -mtime: 1771473096 -commit: 98530511b4f5bb2ebda615f152d1dfd92e468ceb +version: 5.1771589939.8f8502b4 +mtime: 1771589939 +commit: 8f8502b43e06c431ef8c94e26c0160235e4236cd
