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

cachable: make sure to clear caches between tests #16746

Merged

Conversation

apainintheneck
Copy link
Contributor

@apainintheneck apainintheneck commented Feb 26, 2024

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same change?
  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes? Here's an example.
  • Have you successfully run brew style with your changes locally?
  • Have you successfully run brew typecheck with your changes locally?
  • Have you successfully run brew tests with your changes locally?

We've been seeing some difficult to debug test failures recently related to not clearing some caches between tests. Normally we use the Cachable mixin to facilitate caching in classes and modules. I thought it'd be nice to automatically clear those caches between tests without having to create a list manually of all of the classes and modules that use this mixin.

This adds a registry for all modules and classes that cachable is included in. The registry allows us to programmatically clear all caches in between tests so that we don't forget to do that when adding a new class or refactoring code. The goal here is to reduce the number of flaky tests in the future. We only fill this registry when HOMEBREW_TESTS is set in the testing environment.

Marking draft to wait for discussion in #16671 since this will depend on that.

This adds a registry for all modules and classes that
cachable is included in. The registry allows us to
programmatically clear all caches in between tests
so that we don't forget to do that when adding a new
class or refactoring code. The goal here is to reduce
the number of flaky tests in the future.

class << self
Copy link
Member

Choose a reason for hiding this comment

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

Might as well use private_class_method and move everything to def self.* here.

Copy link
Member

Choose a reason for hiding this comment

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

Can be done in a follow-up PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't think that'd work but it seems fine. Good idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I got confused. Yeah, I'll update that too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might not be worth killing the git blame though. I'll do it anyway and see if anyone cares.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bea2dc6

# Collect all classes that mix in Cachable so that those caches can be cleared in-between tests.
if ENV["HOMEBREW_TESTS"]
def self.included(klass)
raise ArgumentError, "Don't use Cachable with singleton classes" if klass.singleton_class?
Copy link
Member

Choose a reason for hiding this comment

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

Why does this need to be special-cased/forbidden?

Copy link
Member

@reitermarkus reitermarkus Feb 26, 2024

Choose a reason for hiding this comment

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

Can we not call klass.extend(self) in here, or is that a different scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think there is a method to get the original class from the singleton class. Forcing classes to extend this module instead of including it in the singleton class avoids that problem and is functionally equivalent.

@@ -11,4 +11,43 @@ def cache
def clear_cache
cache.clear
end

# Collect all classes that mix in Cachable so that those caches can be cleared in-between tests.
if ENV["HOMEBREW_TESTS"]
Copy link
Member

@reitermarkus reitermarkus Feb 26, 2024

Choose a reason for hiding this comment

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

I wonder, could we implement this as a test helper with allow(Cachable).to receive(:extended)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. It needs to be set up before other classes start mixing in this module. I thought of another way of doing this where we load a separate file in test/support/extend/ with these cachable methods before loading global.rb and then we can be sure that this only gets loaded in the test environment.

Copy link
Contributor Author

@apainintheneck apainintheneck Feb 27, 2024

Choose a reason for hiding this comment

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

See 5cc1c85

@apainintheneck apainintheneck marked this pull request as ready for review February 27, 2024 04:32
@apainintheneck
Copy link
Contributor Author

I'm marking this as ready for review since it no longer changes any of the same files as #16671 and should be useful whatever we decide to do there.

Now we only include the cachable registry when running tests.
We basically just load it first and add a bunch of methods to
it before loading the rest of the formula files when we require
global.rb. I added a check to make sure this require order is
preserved.

I also made a bunch of methods private, stop excluding classes
that inherit from casks since it's unnecessary and add more docs.
These were changed to extend to make it easier to determine
where the classes come to in the extended callback but that
means that the file is somewhat inconsistent. On the one
hand we're using class methods and on the other we're extend
self. This cleans that up but now the diff is atrocious and
the blame is even worse. Oh well...
@apainintheneck apainintheneck force-pushed the simplify-cache-clearing-in-tests branch from 909828c to bea2dc6 Compare February 27, 2024 05:23
Copy link
Member

@MikeMcQuaid MikeMcQuaid left a comment

Choose a reason for hiding this comment

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

Good idea, thanks again @apainintheneck!

@MikeMcQuaid MikeMcQuaid merged commit ca9405d into Homebrew:master Feb 27, 2024
26 checks passed
@@ -202,16 +204,7 @@
config.around do |example|
Homebrew.raise_deprecation_exceptions = true

Formulary.clear_cache
Tap.each(&:clear_cache)
Copy link
Member

Choose a reason for hiding this comment

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

@apainintheneck, this needs to stay.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it really necessary to clear taps at the instance level if we're already clearing Tap, CoreTap and CoreCaskTap at the class level? I guess .clear_cache doesn't clear the core tap instances, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is the @instance instance variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

apainintheneck added a commit that referenced this pull request Feb 28, 2024
The core taps exist outside of the normal cache busting cycle
so they need to clear explicitly at the instance level.
Just to be sure we should clear all of them each time.

This essentially reverts part of the change in this PR.
- #16746
@github-actions github-actions bot added the outdated PR was locked due to age label Mar 30, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 30, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated PR was locked due to age
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants