-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Cleanups and Speedup for python unittests #13439
base: master
Are you sure you want to change the base?
Cleanups and Speedup for python unittests #13439
Conversation
I've also reworded a few of the commit messages to fix typos, but I'll wait to re-push so I don't flood the CI |
a94fe1c
to
2f66081
Compare
if is_cygwin(): | ||
self.new_builddir_in_tempdir() |
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.
This commit message doesn't justify why it's okay to allow the umask tests to now run in the source tree by default.
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.
Because it doesn't run on the source tree, and that has nothing to do with the source tree? The source tree is always, unconditionally copied into a new directory, the only thing that changes here is where the build directory is being placed.
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.
This commit message doesn't justify why it's okay to allow the umask tests to now run in the meson.git source tree by default.
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.
And the reason I'm mentioning this is because the comment in the now deleted function says cygwin requires forcing a tempdir for this small subset of tests, it doesn't say anything about the relationship between the directory with meson.build and the one with build.ninja
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.
Okay, your comment confused me, I thought you were suggesting that the test is now configuring from the source tree, not that there were issues with the build directory being in the source tree.
I'm curious as to what the actual original issue with installing in the source dir was, and why it doesn't seem to be a problem now, looking at the code there was a CI regression on cygwin (when we were running cygwin on azure) from this, but it doesn't seem to happen now. I'm really confused
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 don't know either! :D
If we think it's no longer relevant then I'm not opposed to deleting it, it's just the commit message should indicate we are doing so (and why).
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.
Okay, I've added a paragraph about those changes to the commit message.
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.
Interesting and interesting. This failure seems to be sporadic. I'm going to guess there's a race condition involved, but it sounds hard to solve, so I just more-or-less put the workaround back.
@@ -187,7 +187,7 @@ def test_validate_dirs(self): | |||
|
|||
# Using parent as builddir should fail | |||
with self.subTest('parent directory'): | |||
self.builddir = os.path.dirname(self.builddir) | |||
self.builddir = self.common_test_dir |
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 wonder if the solution to this is maybe that the _build_dir_root should be a single base directory in $TMPDIR, and all temporary builddirs should be created inside 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.
But that wont work, this test as always pointed as a parent of the source directory, it just did it in a more round about way.
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 mean that this actually made me think that we should do this:
/tmp/meson-tests-temp-XXXX
/tmp/meson-tests-temp-XXXX/tmpYYYYY
/tmp/meson-tests-temp-XXXX/tmpZZZZZ
If something goes catastrophically wrong in exiting the test harness, it's only one directory to rm -rf manually. And os.path.dirname(self.builddir) would be /tmp/meson-tests-temp-XXXX which is equally under our control, rather than being /tmp (my assumption is you didn't want the # Using parent as builddir should fail
test to suddenly start trying to configure in /tmp itself).
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 actually tried pointing at /tmp initially and it works fine, the call to os.path.basename
works because of the assumption that the build directory is inside the source tree.
It's really unfortunate we can't use pytest, because it has "session" level fixtures that would make this trivial. The best we can do with unittest (I think) is a per module directory, so you'd still end up with:
/tmp/meson-tests-allplatformtests-XXXX/
/tmp/meson-tests-windowstests-XXXX/
/tmp/meson-tests-machinefiletests-XXXX/
unittests/baseplatformtests.py
Outdated
@@ -88,11 +88,11 @@ def setUpClass(cls) -> None: | |||
|
|||
# Misc stuff | |||
if cls.backend is Backend.ninja: | |||
cls.no_rebuild_stdout = ['ninja: no work to do.', 'samu: nothing to do'] | |||
cls.no_rebuild_stdout = frozenset({'ninja: no work to do.', 'samu: nothing to do'}) |
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.
Is there a genuine worry that this is going to be modified? Invoking frozenset() seems interesting in a commit claiming to be speeding things up. ;)
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.
In my benchmarks using frozenset was slightly faster than a normal set for doing the lookups, and was only slightly slower for creating than a regular set. Since the frozenset only gets created once per class that seems like a pretty good tradeoff. If you're still concerned about that, this actually only gets used in edit: it also gets used in WindowsTestallplatformtests.py
, so we could move it to that class to speed things up even more.
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 guess I just wasn't sure why a set isn't good enough, that we need a frozenset. :P
You say a frozenset is slightly faster but I was under the impression that they both use the same CPython implementation and that frozensets are just sets with the mutability methods removed and __hash__
added so that you can use mydict[frozenset({'one'})]
. So I would be surprised if there was any performance difference that wasn't actually environment noise e.g. system load at the time the benchmark was done.
The real difference between the two is that sets have syntax for construction, whereas frozensets always incur a name lookup and function call at the time of creation.
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.
Maybe I've just been doing too much C++ and Rust recently, and would rather have immutable than mutable types by default :D
2f66081
to
a9626a6
Compare
Squashed and re-ordered a bit to address comments from IRC/Matrix. Attempting to fix the one test that is failing on Windows |
099f255
to
8ad08d7
Compare
8ad08d7
to
880b885
Compare
Partial review and partial merge of commits 85e9233...4b76aab |
9a803fa
to
4c9587c
Compare
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.
oops, late...
unittests/platformagnostictests.py
Outdated
with self.subTest('parent directory'): | ||
self.builddir = os.path.dirname(self.builddir) |
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.
This seems nice. Just thought I'd note the commit message for this claims these are "uint tests"...
4c9587c
to
950d96d
Compare
This moves the cleanup callback to the point the file is being created, which is more accurate, and doesn't require adding `if os.path.exists()` calls
Rather than relying on callers to cleanup after the helper.
To get better error messages
Stop thrashing users SSDs because Windows AV is broken. This also helps IDEs that try to keep up with the quickly created and destroyed files. Users on Windows and Cygwin who don't have catastrophically bad AV solutions can use the MESON_UNIT_TEST_FORCE_TMPDIR=1 environment variable to get better behavior.
Set lookups are faster for membership checks.
This avoids having to set them up in cases we don't use them, or override them in cases where we change the build directory
950d96d
to
6dce000
Compare
@@ -268,15 +268,13 @@ def test_subproject_variables(self): | |||
''' | |||
tdir = os.path.join(self.unit_test_dir, '20 subproj dep variables') | |||
stray_file = os.path.join(tdir, 'subprojects/subsubproject.wrap') | |||
if os.path.exists(stray_file): | |||
windows_proof_rm(stray_file) | |||
self.addCleanup(windows_proof_rm, stray_file) |
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.
This changes behaviour. The old code ensured that if the file was already there for whatever reason it would be deleted before the test was run.
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.
Indeed, it's changing behavior. The current behavior is:
- the test creates the file
- the test runs, and makes no effort to delete the file
- on re-run the test deletes the file
This is weird, and not something that we generally do.
This changes the behavior to match what basically all tests do, and what should happen:
- the test creates the file
- the test registers a callback to ensure that the file is cleaned up when the test exits, regardless of whether that is in error or in success
- on re-run the test doesn't need to look for the file, because it doesn't exist.
We have tons of tests that make the assumption "files I create as part of my test process do no pre-exist", and will die horribly, or worse, fail silently if they do.
Other than the one change LGTM. |
I initially set out to just do some long overdue maintenance of our unit tests, along way I cleaned up a number of issues that have been bugging me for a while:
UnitTest.addCleanup
For me the end result is a reduction in a full unit test run time by ~15%, and there are no stray files left in the source tree compared to the main branch.
I wont be surprised if there are a few regressions on Windows to clean up.