-
Notifications
You must be signed in to change notification settings - Fork 788
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
Test flakiness investigation and attempted fixes ❄ #3498
Changes from all commits
6b02725
37ff2cf
f113057
be865f9
120feb0
8525c53
2539df9
842bdf8
ab9f1cd
65d7853
0b4f3a9
3741f59
b73f69c
86da626
0faf7b2
8ecbe20
fa36cd4
c0fef47
2df0584
5b0d78e
cda2319
6c686ff
68f2d2e
d871c83
710d6b5
1a7feec
b8428dd
55c0950
ab28264
4d64ca9
441882d
91a5936
5238dd9
7674b4f
0df54ef
63bcf41
d0f2971
eb15a59
cba8978
c837abe
6d06d2a
1d4c28e
c3766a9
2e93238
5642681
5dd2f81
59572c7
62ac7e2
a7bb953
4b9bce3
0582f54
65c97c8
b4de92b
9ec4680
15c308d
547a918
6104481
d9feeda
1238870
c5be6af
c238dac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from __future__ import annotations | ||
|
||
from asyncio import Future, gather | ||
from typing import Any, Coroutine, Iterator, TypeVar | ||
|
||
import rich.repr | ||
|
||
ReturnType = TypeVar("ReturnType") | ||
|
||
|
||
@rich.repr.auto(angular=True) | ||
class AwaitComplete: | ||
darrenburns marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""An 'optionally-awaitable' object.""" | ||
|
||
def __init__(self, *coroutines: Coroutine[Any, Any, Any]) -> None: | ||
"""Create an AwaitComplete. | ||
|
||
Args: | ||
coroutine: One or more coroutines to execute. | ||
""" | ||
self.coroutines = coroutines | ||
self._future: Future = gather(*self.coroutines) | ||
|
||
async def __call__(self) -> Any: | ||
return await self | ||
|
||
def __await__(self) -> Iterator[None]: | ||
return self._future.__await__() | ||
|
||
@property | ||
def is_done(self) -> bool: | ||
"""Returns True if the task has completed.""" | ||
return self._future.done() | ||
|
||
@property | ||
def exception(self) -> BaseException | None: | ||
"""An exception if it occurred in any of the coroutines.""" | ||
if self._future.done(): | ||
return self._future.exception() | ||
return None | ||
|
||
@classmethod | ||
def nothing(cls): | ||
"""Returns an already completed instance of AwaitComplete.""" | ||
instance = cls() | ||
instance._future = Future() | ||
instance._future.set_result(None) # Mark it as completed with no result | ||
return instance |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ | |
from pathlib import Path | ||
from typing import TYPE_CHECKING, Callable, ClassVar, Iterable, Iterator | ||
|
||
from ..await_complete import AwaitComplete | ||
|
||
if TYPE_CHECKING: | ||
from typing_extensions import Self | ||
|
||
|
@@ -152,18 +154,26 @@ def __init__( | |
) | ||
self.path = path | ||
|
||
def _add_to_load_queue(self, node: TreeNode[DirEntry]) -> None: | ||
def _add_to_load_queue(self, node: TreeNode[DirEntry]) -> AwaitComplete: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstring is missing the return. I don't know how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, if you call
rodrigogiraoserrao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Add the given node to the load queue. | ||
|
||
The return value can optionally be awaited until the queue is empty. | ||
|
||
Args: | ||
node: The node to add to the load queue. | ||
|
||
Returns: | ||
An optionally awaitable object that can be awaited until the | ||
load queue has finished processing. | ||
""" | ||
assert node.data is not None | ||
if not node.data.loaded: | ||
node.data.loaded = True | ||
self._load_queue.put_nowait(node) | ||
|
||
def reload(self) -> None: | ||
return AwaitComplete(self._load_queue.join()) | ||
|
||
def reload(self) -> AwaitComplete: | ||
darrenburns marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Reload the `DirectoryTree` contents.""" | ||
self.reset(str(self.path), DirEntry(self.PATH(self.path))) | ||
# Orphan the old queue... | ||
|
@@ -172,7 +182,8 @@ def reload(self) -> None: | |
self._loader() | ||
# We have a fresh queue, we have a fresh loader, get the fresh root | ||
# loading up. | ||
self._add_to_load_queue(self.root) | ||
queue_processed = self._add_to_load_queue(self.root) | ||
return queue_processed | ||
|
||
def clear_node(self, node: TreeNode[DirEntry]) -> Self: | ||
"""Clear all nodes under the given node. | ||
|
@@ -202,6 +213,7 @@ def reset_node( | |
"""Clear the subtree and reset the given node. | ||
|
||
Args: | ||
node: The node to reset. | ||
label: The label for the node. | ||
data: Optional data for the node. | ||
|
||
|
@@ -213,16 +225,20 @@ def reset_node( | |
node.data = data | ||
return self | ||
|
||
def reload_node(self, node: TreeNode[DirEntry]) -> None: | ||
def reload_node(self, node: TreeNode[DirEntry]) -> AwaitComplete: | ||
darrenburns marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Reload the given node's contents. | ||
|
||
The return value may be awaited to ensure the DirectoryTree has reached | ||
a stable state and is no longer performing any node reloading (of this node | ||
or any other nodes). | ||
|
||
Args: | ||
node: The node to reload. | ||
""" | ||
self.reset_node( | ||
node, str(node.data.path.name), DirEntry(self.PATH(node.data.path)) | ||
) | ||
self._add_to_load_queue(node) | ||
return self._add_to_load_queue(node) | ||
|
||
def validate_path(self, path: str | Path) -> Path: | ||
"""Ensure that the path is of the `Path` type. | ||
|
@@ -239,13 +255,13 @@ def validate_path(self, path: str | Path) -> Path: | |
""" | ||
return self.PATH(path) | ||
|
||
def watch_path(self) -> None: | ||
async def watch_path(self) -> None: | ||
"""Watch for changes to the `path` of the directory tree. | ||
|
||
If the path is changed the directory tree will be repopulated using | ||
the new value as the root. | ||
""" | ||
self.reload() | ||
await self.reload() | ||
|
||
def process_label(self, label: TextType) -> Text: | ||
"""Process a str or Text into a label. Maybe overridden in a subclass to modify how labels are rendered. | ||
|
@@ -421,16 +437,17 @@ async def _loader(self) -> None: | |
# the tree. | ||
if content: | ||
self._populate_node(node, content) | ||
# Mark this iteration as done. | ||
self._load_queue.task_done() | ||
finally: | ||
# Mark this iteration as done. | ||
self._load_queue.task_done() | ||
|
||
def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None: | ||
async def _on_tree_node_expanded(self, event: Tree.NodeExpanded) -> None: | ||
event.stop() | ||
dir_entry = event.node.data | ||
if dir_entry is None: | ||
return | ||
if self._safe_is_dir(dir_entry.path): | ||
self._add_to_load_queue(event.node) | ||
await self._add_to_load_queue(event.node) | ||
else: | ||
self.post_message(self.FileSelected(event.node, dir_entry.path)) | ||
|
||
|
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 it would be helpful if the repr included the coroutines.
Maybe see what it generates. If it looks like noise, we can leave it as is.
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.
Wouldn't it already include the coroutines, since the parameter is named the same as the attribute?
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.
Ah, you're right. What a nice feature. I must thank the dev who implemented that.
Curious what the repr will look like.