-
Notifications
You must be signed in to change notification settings - Fork 162
Improve logging of requires #4430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
I'd say it should be separated, wth proper messaging, e.g. "failed to install required packages, see the details below, we're done here" vs "failed to install recommended packages, see the details below, and let's move on".
Yes. We should have a test somewhere checking whether a non-existent package failed to install, it should be enough to extend it with an assert or two to check for new messages. |
795a3dc to
b46f5c7
Compare
|
I have addressed comments and suggestions in b46f5c7 For the "unattributed_packages" - I don't know if that can even happen that packages would not be required/recommended - but maybe the "essential requires" like |
|
I've addressed the comments in latest commits, now extending the tests remains. |
|
There are some conflicts.
Here is an example change: def extract_package_name_from_package_manager_output(self, output: str) -> Iterator[str]:
"""Extract package names from package manager output using regex patterns."""
self.logger.debug(f"Analyzing package manager output for failed packages:\n{output}")
matched_packages = []
for i, pattern in enumerate(self._PACKAGE_NAME_IN_PM_OUTPUT_PATTERNS):
pattern_matches = []
for match in pattern.finditer(output):
package_name = match.group(1)
pattern_matches.append(package_name)
matched_packages.append(package_name)
yield package_name
if pattern_matches:
self.logger.debug(f"Pattern {i} matched packages: {pattern_matches}")
else:
self.logger.debug(f"Pattern {i} found no matches")
if not matched_packages:
self.logger.debug("No failed packages extracted from output")
else:
self.logger.debug(f"Total extracted packages: {matched_packages}") |
skycastlelily
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than the nitpick and extending test suggestion, LGTM
| for dependency in test.require: | ||
| required_dependencies_to_tests[str(dependency)].append(test_name) | ||
| for dependency in test.recommend: | ||
| recommended_dependencies_to_tests[(str(dependency))].append(test_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would include the library dependency types also
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does work with uninstallable library:
package: 5 packages requested
bash
coreutils
grep
library(yum/false-library)
sed
Required packages failed to install, aborting:
library(yum/false-library): required by: /tests/FIRST
fail: Command 'rpm -q --whatprovides coreutils 'library(yum/false-library)' sed grep bash || dnf5 install -y coreutils 'library(yum/false-library)' sed grep bash' returned 1.
however only if the library is requested in this form: library(yum/false-library).
Due to recent changes - beaker-tasks repository being dropped from Testing Farm Image Mode - this format is now needed:
require:
- type: library
url: https://github.com/beakerlib/yum
name: /false-library
However this ends up with After beakerlib processing, tests may have only simple requirements error, which is kind of confusing for the user. It originates from Prepare class where it checks whether dependency is 'simple' and throws an exception if it isn't, so it does not have a chance to pass the info to anything else. Not sure how to proceed here..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does work with uninstallable library:
package: 5 packages requested bash coreutils grep library(yum/false-library) sed Required packages failed to install, aborting: library(yum/false-library): required by: /tests/FIRST fail: Command 'rpm -q --whatprovides coreutils 'library(yum/false-library)' sed grep bash || dnf5 install -y coreutils 'library(yum/false-library)' sed grep bash' returned 1.however only if the library is requested in this form:
library(yum/false-library). Due to recent changes - beaker-tasks repository being dropped from Testing Farm Image Mode - this format is now needed:require: - type: library url: https://github.com/beakerlib/yum name: /false-libraryHowever this ends up with
After beakerlib processing, tests may have only simple requirementserror, which is kind of confusing for the user. It originates from Prepare class where it checks whether dependency is 'simple' and throws an exception if it isn't, so it does not have a chance to pass the info to anything else. Not sure how to proceed here..
This error message appears because the library was either not recognized, or failed to install or something similar, and remained on the list. First, the libraries are resolved, cloned, installed, etc., and removed from require and recommend lists; then, this check verifies only the package-like items are left. So something is not working correctly in the processing of libraries on the list of required packages.
@LecrisUT could it be connected to one of your PRs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to recent changes - beaker-tasks repository being dropped from Testing Farm Image Mode - this format is now needed:
Would be nice if we can officially drop it. I have not done the impact-check. On the Fedora side there is none, so probably beaker-tasks is the only provider for those?
However this ends up with
After beakerlib processing, tests may have only simple requirementserror, which is kind of confusing for the user. It originates from Prepare class where it checks whether dependency is 'simple' and throws an exception if it isn't, so it does not have a chance to pass the info to anything else. Not sure how to proceed here..
The error handling for that should be done in the discover step directly, not on the PrepareInstall. I need to check what is going on there, because I thought that that would already be the case, can you help me with a minimum reproducer?
As for proceeding with this, can you filter out all Library types, and for now, check explicitly for the BeakerlibLibrary + .format == "rpm", as those are the only sources of library(...). All the other Library types we drop and try to revisit after the Library refactors.
So something is not working correctly in the processing of libraries on the list of required packages.
@LecrisUT could it be connected to one of your PRs?
Yes and no. I have not touched that logic yet, so cannot say for certain what is going on, but something is wrong for sure. The main logic that is handling Library dependencies is in
Lines 140 to 149 in 746105f
| def dependencies( | |
| *, | |
| original_require: list[Dependency], | |
| original_recommend: Optional[list[Dependency]] = None, | |
| parent: Optional[tmt.utils.Common] = None, | |
| imported_lib_ids: ImportedIdentifiersType = None, | |
| logger: tmt.log.Logger, | |
| source_location: Optional[Path] = None, | |
| target_location: Optional[Path] = None, | |
| ) -> LibraryDependenciesType: |
tmt/tmt/steps/discover/__init__.py
Lines 450 to 456 in 746105f
| def install_libraries(self, source: Path, target: Path) -> None: | |
| """ | |
| Install required beakerlib libraries for discovered tests. | |
| :param source: Source directory of the tests. | |
| :param target: Target directory where libraries should be installed. | |
| """ |
My understanding so far is that it is trying to go through the list and pick out all the
Library types and drop all library types unless they were defined as library(yum/false-library) in which case forward it as a normal dependency for PrepareInstall to handle 1. Thinking about it more, normal Library types that are not BeakerlibLibrary + .format == "rpm" should really not be present there, so maybe there is a regression, let me check more closely.
Footnotes
-
The logic in there is super convoluted and unnecessary, will try to refactor it ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reproducer: any test with non-existing library likes this
- type: library
url: https://github.com/beakerlib/yum
name: /false-library
or just
- url: https://github.com/beakerlib/yum
name: /false-library
in require section of its main.fmf. Running it results in https://github.com/teemtee/tmt/blob/main/tmt/base.py#L712-L735 throwing
fail: Invalid requirement: DependencyFmfId(fmf_root=None, git_root=None, default_branch=None, url='https://github.com/beakerlib/yum', ref=None, path=None, name='/false-library', destination=None, nick=None, type='library')
plus the exception
plan failed.
The exception was caused by 1 earlier exceptions
Cause number 1:
After beakerlib processing, tests may have only simple requirements
coming from here https://github.com/teemtee/tmt/blob/main/tmt/steps/prepare/__init__.py#L275-L317
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'd expect these library-like requirements be consumed by the layer responsible for beakerlib libraries and other complex requirements.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made code changes to reflect the libraries problem. Now it raises at a Discover level without passing anything to Prepare.
Having uninstallable library results in:
fail: Failed to process beakerlib libraries (/bad-library) for test '/tests/SECOND', '/tests/THIRD'.
fail: Failed to process beakerlib libraries (/false-library) for test '/tests/THIRD'.
fail: Failed to process beakerlib libraries (/wrong-library) for test '/tests/FIRST', '/tests/SECOND', '/tests/THIRD'.
report
how: display
summary: no results found
cleanup
warn: Nothing to cleanup, no guests provisioned.
plan failed.
The exception was caused by 1 earlier exceptions
Cause number 1:
Failed to process beakerlib libraries.
34eae31 to
b86927f
Compare
b86927f to
2c6a8b2
Compare
| _UNABLE_TO_LOCATE_PATTERN = re.compile(r'unable to locate package\s+([^\s]+)', re.IGNORECASE) | ||
| _NO_SUCH_PACKAGE_PATTERN = re.compile(r'ERROR:\s+([^\s:]+):\s+No such package', re.IGNORECASE) | ||
|
|
||
| _FAILED_PACKAGE_INSTALLATION_PATTERNS = [_UNABLE_TO_LOCATE_PATTERN, _NO_SUCH_PACKAGE_PATTERN] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More polishing :) If _UNABLE_TO_LOCATE_PATTERN is not used, it does not need to have a name, and it can be simply an item of the _FAILED_PACKAGE_INSTALLATION_PATTERNS list:
_FAILED_PACKAGE_INSTALLATION_PATTERNS = [
re.compile(r'unable to locate package\s+([^\s]+)', re.IGNORECASE),
re.compile(r'ERROR:\s+([^\s:]+):\s+No such package', re.IGNORECASE)
]| ) | ||
|
|
||
| for line in lines: | ||
| self._logger.fail(line) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can emit these lines right from the loop above, no need to collect them in lines.
| } | ||
|
|
||
| for library_name in library_names: | ||
| library_failures[library_name].add(test.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice to also report the other unresolved dependencies - we learn about libraries only, but since we are in the business of reporting unresolved libraries, we can take it all the way. There's at least DependencyFile. Maybe it would be possible to collect unresolved dependencies in general, e.g. separate them into buckets by their class names, then by their "name", and collect "their" tests there? A final report would do the libraries in some nice way, then the DependencyFile, and then if there's any other class name left, some generic "don't know how to present it nicer yet, but this we failed to resolve" output.
Maybe something along the following:
# Collect all unresolved dependencies, per class, each dependency with its own list of tests.
unresolved_dependencies = collections.defaultdict(lambda: collections.defaultdict(list))
...
for dependency in (*test.require, *test.recommend):
if isinstance(dependency, DependencySimple):
continue
unresolved_dependencies[dependency.__class__.__name__][dependency].append(test)
...
# This is the end, right?
# Report unresolved libraries
for dependency, tests in unresolved_dependencies.pop(tmt.base.DependencyFmfId.__name__, {}).items():
test_names = ', '.join(f"'{name}'" for name in sorted(test.name for test in tests)
self._logger.fail(
f"Failed to process beakerlib library {dependency.name or "/"}), required by tests {test_names}."
)
if unresolved_dependencies:
# Rough reporting for dependencies we don't know how to report in a better way
...
Pull Request Checklist
dependencies_to_testgathers both requires + recommends and tmt is going to fail when something could not be installed in the require phase already. If all required packages are available to install, but a recommended one is not, then it will fail also, but the message{package}: required by {test_name}might be a bit misleading, because it is not required, only recommended. Should the messaging be handled separately or is simply "needed by" enough to cover both requires+recommend in general?Output:
Test coverage needed?
Fixes #4302