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 flaky options_bootstrapper_test.py #9220

Merged

Conversation

Eric-Arellano
Copy link
Contributor

test_full_options_caching would frequently flake because we were using sets internally in the Options and OptionsBootstrapper types. Sets have unstable ordering, so sometimes the test would pass and sometimes it would fail.

We fix this by using our OrderedSet and FrozenOrderedSet types, with sorting before passing to those constructors.

@@ -73,16 +74,16 @@ class DuplicateScopeError(Exception):
"""More than one registration occurred for the same scope."""

@classmethod
def complete_scopes(cls, scope_infos: Iterable[ScopeInfo]) -> Set[ScopeInfo]:
def complete_scopes(cls, scope_infos: Iterable[ScopeInfo]) -> FrozenOrderedSet[ScopeInfo]:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sometimes it's fine to return a Set, but generally FrozenOrderedSet is a much safer default. Especially with the engine, we want stable iteration order for caching.

@@ -213,7 +214,7 @@ def get_bootstrap_options(self) -> Options:
return self.bootstrap_options

@memoized_method
def _full_options(self, known_scope_infos: Tuple[ScopeInfo, ...]) -> Options:
def _full_options(self, known_scope_infos: FrozenOrderedSet[ScopeInfo]) -> Options:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This cannot be Iterable or Sequence because @memoized_method requires a hashable type so passing something like a list—then relying on the downstream implementation to sort—would fail.

Comment on lines -242 to +245
return self._full_options(tuple(set(known_scope_infos)))
return self._full_options(
FrozenOrderedSet(sorted(known_scope_infos, key=lambda si: si.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.

This change is key. Previously, we were using set() to get the de-duplication and tuple to make it hashable. But, we weren't getting stable iteration order. Now we get all 3!

Comment on lines 265 to 267
all_options_under_scope = set(config.values.options(section)) - set(
config.values.defaults
)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

NB: we still use set here because they're inlined variables and only ever used for checking x in xs. There, it's simpler and faster to use the built-in set type.

@@ -262,7 +265,7 @@ def verify_configs_against_options(self, options: Options) -> None:
all_options_under_scope = set(config.values.options(section)) - set(
config.values.defaults
)
for option in all_options_under_scope:
for option in sorted(all_options_under_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.

Not relevant to the flake, but this means our config validation error will now be deterministic!

Copy link
Contributor

Choose a reason for hiding this comment

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

yay determinism!

@Eric-Arellano Eric-Arellano merged commit 6a81a2d into pantsbuild:master Mar 4, 2020
@Eric-Arellano Eric-Arellano deleted the fix-flaky-options-test branch March 4, 2020 13:11
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.

None yet

3 participants