Skip to content
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

Fix skipped tests on Windows #412

Merged
merged 8 commits into from
Mar 11, 2024
Merged

Fix skipped tests on Windows #412

merged 8 commits into from
Mar 11, 2024

Conversation

jherland
Copy link
Member

Fix tests that were skipped on Windows until now.

These were not skipped due to lack of Windows compatibility in FalwtyDeps itself, but rather due to lack of compatibility (i.e. latent POSIX assumptions) in our test framework.

@jherland jherland force-pushed the jherland/fix-tests-on-windows branch 7 times, most recently from e8431df to 4c522c1 Compare January 13, 2024 12:26
@jherland
Copy link
Member Author

jherland commented Jan 15, 2024

Crap, while trying to fix test_real_projects on Windows I've run into an issue that is not strictly Windows-specific (although it only show up on Windows here), and that will require more thinking/changes in our core logic. (@mknorps: I suspect you might have run into the same issue while working on the initial Windows support). Here it is:

Tensorflow is weird on Windows: On other systems, it's "just" a large package (several hundred MBs), but on Windows, it is a very small (1.9kB) placeholder package that does not actually provide a tensorflow import name at all, rather it depends on another package, tensorflow-intel, which contains the actual meat (266MB). Since we pip install with --no-deps, we would need to explicitly include that package in our test requirements order to make the tensorflow import name available on Windows.

Howver, this is not enough: Our analysis would still see that the tensorflow package does not proved the tensorflow import name, and tensorflow ends up BOTH as undeclared AND unused!

To fix this, I think FawltyDeps needs to somehow realize the dependency relationship between the placeholder package (tensorflow) and its dependency (tensorflow-intel), and somehow attribute the latter's import names to the former.

This is hard, because a lot of packages have dependencies that are unrelated to the import name they are "expected" to provide, so we might have to make a corner case/exception for "placeholder packages" here... More thinking needed...


After some more thinking I realize I've seen this issue before. It is pretty much exactly the same as #176!

Anyway, I don't plan to fix this deeper issue as part of this PR. We will need to work around this issue for the time being (probably simply skip this test on Windows).

@jherland jherland force-pushed the jherland/integrate_ignore_parser branch 2 times, most recently from ee2f663 to 1049383 Compare January 19, 2024 13:08
Base automatically changed from jherland/integrate_ignore_parser to main January 19, 2024 13:19
@jherland jherland force-pushed the jherland/fix-tests-on-windows branch 4 times, most recently from b48b435 to 43b7b62 Compare January 19, 2024 22:17
@jherland jherland marked this pull request as ready for review January 19, 2024 22:27
@jherland jherland linked an issue Jan 20, 2024 that may be closed by this pull request
@jherland jherland force-pushed the jherland/fix-tests-on-windows branch from e691587 to 2c02409 Compare January 20, 2024 01:14
@jherland jherland linked an issue Jan 20, 2024 that may be closed by this pull request
@jherland jherland force-pushed the jherland/fix-tests-on-windows branch from 2c02409 to bd772e7 Compare January 25, 2024 12:22
@jherland jherland force-pushed the jherland/fix-tests-on-windows branch from bd772e7 to 7b2518f Compare February 29, 2024 08:15
@jherland jherland changed the base branch from main to jherland/qiskit-1.0-fix February 29, 2024 08:15
Base automatically changed from jherland/qiskit-1.0-fix to main March 4, 2024 08:25
Copy link
Collaborator

@mknorps mknorps left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Results of tests on Windows.

Before:

=================================================================== short test summary info ====================================================================
FAILED tests/test_gitignore_parser.py::test_symlink_to_another_directory - OSError: [WinError 1314] A required privilege is not held by the client: 'C:\\Users\\ttyle\\AppData\\Local\\Temp\\pytest-of-ttyle\\pytest-160\\test_symlink_...
===================================== 1 failed, 1079 passed, 146 skipped, 42 deselected, 77 warnings in 134.14s (0:02:14) ======================================

After:

=================================================================== short test summary info ====================================================================
FAILED tests/test_gitignore_parser.py::test_symlink_to_another_directory - OSError: [WinError 1314] A required privilege is not held by the client: 'C:\\Users\\ttyle\\AppData\\Local\\Temp\\pytest-of-ttyle\\pytest-161\\test_symlink_...
====================================== 1 failed, 1193 passed, 60 skipped, 48 deselected, 85 warnings in 135.54s (0:02:15) ======================================

There is one test that is failing due to symlink, but let's skip it for now as it was the problem also in my "vanilla" run.

@mknorps
Copy link
Collaborator

mknorps commented Mar 5, 2024

I include the encountered failure for the record here:


=========================================================================== FAILURES ===========================================================================
______________________________________________________________ test_symlink_to_another_directory _______________________________________________________________

tmp_path = WindowsPath('C:/Users/ttyle/AppData/Local/Temp/pytest-of-ttyle/pytest-161/test_symlink_to_another_direct0')

    def test_symlink_to_another_directory(tmp_path):
        project_dir = tmp_path / "project_dir"
        link = project_dir / "link"
        target = tmp_path / "another_dir/target"

        project_dir.mkdir(parents=True, exist_ok=True)
>       link.symlink_to(target)

tests\test_gitignore_parser.py:240:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = WindowsPath('C:/Users/ttyle/AppData/Local/Temp/pytest-of-ttyle/pytest-161/test_symlink_to_another_direct0/project_dir/link')
target = WindowsPath('C:/Users/ttyle/AppData/Local/Temp/pytest-of-ttyle/pytest-161/test_symlink_to_another_direct0/another_dir/target')
target_is_directory = False

    def symlink_to(self, target, target_is_directory=False):
        """
        Make this path a symlink pointing to the target path.
        Note the order of arguments (link, target) is the reverse of os.symlink.
        """
        if not hasattr(os, "symlink"):
            raise NotImplementedError("os.symlink() not available on this system")
>       os.symlink(target, self, target_is_directory)
E       OSError: [WinError 1314] A required privilege is not held by the client: 'C:\\Users\\ttyle\\AppData\\Local\\Temp\\pytest-of-ttyle\\pytest-161\\test_symlink_to_another_direct0\\another_dir\\target' -> 'C:\\Users\\ttyle\\AppData\\Local\\Temp\\pytest-of-ttyle\\pytest-161\\test_symlink_to_another_direct0\\project_dir\\link'

..\..\..\AppData\Local\Programs\Python\Python312\Lib\pathlib.py:1387: OSError
======================================================================= warnings summary =======================================================================
fawltydeps\extract_declared_dependencies.py:16
  C:\Users\ttyle\programming\FawltyDeps2\FawltyDeps\fawltydeps\extract_declared_dependencies.py:16: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
    from pkg_resources import Requirement

tests/test_cmdline.py: 5 warnings
tests/test_cmdline_options.py: 17 warnings
tests/test_extract_declared_dependencies_errors.py: 12 warnings
tests/test_extract_declared_dependencies_success.py: 50 warnings
  C:\Users\ttyle\programming\FawltyDeps2\FawltyDeps\fawltydeps\limited_eval.py:86: DeprecationWarning: ast.Str is deprecated and will be removed in Python 3.14; use ast.Constant instead
    if isinstance(node, (ast.Constant, ast.Str)):

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

Copy link
Collaborator

@mknorps mknorps left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome 🎉

It must have took a lot of work to go through all those tests and fix them. Thank you @jherland !

venv_commands function is a good improvement. I really liked how you utilized touch from PathLib and was left with only one line of Windows/Posix distinction as an if statement.

I tested how the updated FawltyDeps works on Windows, and it looks really good 💯

I have one nit-picking comment, that you can act on either by commenting somewhere in BaseExperiment and BaseProject or change the structure of those classes. The latter may be nicer but more time consuming.
The problem is: what if you have both flags posix_only, windows_only in place? Should you skip on both systems or run on both systems?
What if instead of those two flags you had:

systems = ["posix", "windows"]

with default both options and the systems choice will be narrowed down by systems = ["posix]`

tests/project_helpers.py Outdated Show resolved Hide resolved
tests/project_helpers.py Show resolved Hide resolved
tests/test_traverse_project.py Show resolved Hide resolved
Add the optional "posix_only" and "windows_only" boolean flags to the
TOML schema for test_sample_projects and test_real_projects. This flag
can be set either at the project level, or at the experiment level, and
will cause the associated experiments to be skipped on an unsupported
platform.
jherland added 5 commits March 7, 2024 12:09
For sample projects that contain examples of virtualenvs, construct twin
sample projects with corresponding Windows layout of these virtualenvs.

Use the newly added `posix_only` and `windows_only` directives in
expected.toml to limit testing of these projects to the appropriate
platform.
Use the sample projects added in the previous commit to create separate
test cases for Python environment layouts on Windows vs POSIX.
The python-algortihms project highlights an outstanding issue in
FawltyDeps: Placeholder packages. As discussed in issue #176, these are
packages that don't provide the expected import name (or any import name
at all), but instead depend on other packages (transitive dependencies)
which will then supply the relevant import name.

The first example we came across of such a package (in #176) was qiskit.
Another example is tensorflow, but only on Windows: On Windows the
tensorflow package is a 1.9kB placeholder that depends on
tensorflow-intel (266MB) to bring in the actual meat. (On POSIX, the
tensorflow package itself contains everything.)

We make two changes to our real_projects test for python-algorithms:

1. We add the posix_only flag to the all_reqs_installed experiment, to
   prevent it from running on Windows. Its expected results would be
   different there (because of tensorflow-intel).

2. We add a new experiment, some_reqs_customized, which uses a
   custom_mapping to resolve qiskit and tensorflow. The remaining
   requirements are installed (as in the previous experiment).
   This yields the "best" FawltyDeps result for this project.
   The remaining undeclared/unused dependencies are presumed to be
   true positives for this project.
This fixes issue #406.

Instead of generating lines of a shell script that need to be run with
shell=True in subprocess.run(), generate a sequence of argv-style
commands. These are safer (no shell involved) and more easily portable
to Windows (for one, our quoting of command line args on Windows was
dubious, at best...).

Also move the first and last commands in the previous shell script
(i.e. the preliminary removal of venv_dir, and the creation of the
venv_dir/.installed marker file) out of venv_script_lines() and into
the __call__() method.

Since these are no longer part of the generated commands, they are also
no longer part of the venv_hash(). If we make changes to these two steps
(without making any other changes that are captured by venv_hash(), we
will end up reusing a cache entry. If this is problematic, then some
other modification to change the venv_hash() must be added at the same
time.
@jherland jherland force-pushed the jherland/fix-tests-on-windows branch 3 times, most recently from 501c750 to ba91104 Compare March 8, 2024 12:46
Represent posix/windows compatibility as a proper enum.Flag that allows
test projects/experiments to declare their compatibility as either:

  - Compatibility.POSIX for Posix-only tests
  - Compatibility.WINDOWS for Windows-only tests
  - (Compatibility.POSIX | Compatibility.WINDOWS) for tests that can be
    run on either Posix or Windows (this is the default).

The compatibility of the current experiment is found by looking up the
.compatibility member of the experiment, or if unset, falling back to
the project's .compatibility member (which defaults to compatibility
with everything).

We then convert the current platform (sys.platform) into either
Compatibility.POSIX or Compatibility.WINDOWS, and check this against the
compatibility of the current experiment to see if if should be skipped
or not.

This allows adding further compatiblity flags in the future without
having to add more directives to the TOML schema for projects and
experiments.
@jherland jherland force-pushed the jherland/fix-tests-on-windows branch from ba91104 to e44a405 Compare March 8, 2024 13:58
@jherland
Copy link
Member Author

jherland commented Mar 8, 2024

The problem is: what if you have both flags posix_only, windows_only in place? Should you skip on both systems or run on both systems?

I did have this check, that would fail the test if both flags were set: https://github.com/tweag/FawltyDeps/pull/412/files#diff-96a370372e6f28b33cfb75c3357c881efb4982beb0b5cebe203869ff85e7ffc6R292

But, really, I agree with you that the data structure representing this should be improved.

What if instead of those two flags you had:

systems = ["posix", "windows"]

with default both options and the systems choice will be narrowed down by systems = ["posix]`

Yes, this is much closer to what we want. Furthermore, we should try to replace the strings with "more proper" enum values. Fortunately there is something for this situation in the standard library: enum.Flag allows you to create an enum and furthermore combine the enum values within the same type!

That is, instead of creating a Compatibility Enum type, and the using a set of {Compatibility.POSIX, Compatibility.WINDOWS} to represent the default/full compatibility, we can create a Compatibility Flag type that uses bitwise OR to combine flags and the combination remains a valid Flag value: Compatibility.POSIX | Compatibility.WINDOWS is a valid value.

e44a405 contains the details. 😄

@jherland jherland merged commit ef2b410 into main Mar 11, 2024
65 checks passed
@jherland jherland deleted the jherland/fix-tests-on-windows branch March 11, 2024 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix test_traverse_project.py on Windows Refactor real_projects tests to support Windows
2 participants