test: 完善多份测试用例的类型标注与校验逻辑

1. 为多个测试函数补充pytest.CaptureFixture[str]类型注解
2. 为graphlib类型声明文件补全方法参数类型
3. 为pdftool测试的mock函数添加Any类型标注
4. 新增数据库连接非空校验断言
5. 优化emlmanager测试的字典展开格式与修复decode测试bug
6. 为gittool测试添加命令类型列表校验
7. 为envrs测试添加pyrefly忽略注释
This commit is contained in:
2026-06-26 21:57:44 +08:00
parent 7ded8df05e
commit 22ac9fc4dd
8 changed files with 211 additions and 228 deletions
+8 -7
View File
@@ -7,6 +7,7 @@ from unittest.mock import patch
import pytest import pytest
import pyflowx as px
from pyflowx.cli import bumpversion from pyflowx.cli import bumpversion
@@ -76,7 +77,7 @@ class TestBumpFileVersion:
content = test_file.read_text(encoding="utf-8") content = test_file.read_text(encoding="utf-8")
assert "build" not in content assert "build" not in content
def test_no_version_found(self, tmp_path: Path, capsys) -> None: def test_no_version_found(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""Should return None when no version pattern found.""" """Should return None when no version pattern found."""
test_file = tmp_path / "test.txt" test_file = tmp_path / "test.txt"
test_file.write_text("no version here", encoding="utf-8") test_file.write_text("no version here", encoding="utf-8")
@@ -149,7 +150,7 @@ dependencies = ["lib >= 2.0.0", "other >= 3.0.0"]
assert "lib >= 2.0.0" in updated assert "lib >= 2.0.0" in updated
assert "other >= 3.0.0" in updated assert "other >= 3.0.0" in updated
def test_file_read_error(self, tmp_path: Path, capsys) -> None: def test_file_read_error(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""Should handle file read errors.""" """Should handle file read errors."""
# 创建一个目录而不是文件 # 创建一个目录而不是文件
test_file = tmp_path / "test_dir" test_file = tmp_path / "test_dir"
@@ -158,7 +159,7 @@ dependencies = ["lib >= 2.0.0", "other >= 3.0.0"]
with pytest.raises(Exception): # noqa: B017 with pytest.raises(Exception): # noqa: B017
bumpversion.bump_file_version(test_file, "patch") bumpversion.bump_file_version(test_file, "patch")
def test_file_write_error(self, tmp_path: Path, capsys) -> None: def test_file_write_error(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""Should handle file write errors.""" """Should handle file write errors."""
# 在只读目录中创建文件(这个测试在某些系统上可能不适用) # 在只读目录中创建文件(这个测试在某些系统上可能不适用)
test_file = tmp_path / "readonly.toml" test_file = tmp_path / "readonly.toml"
@@ -224,7 +225,7 @@ class TestVersionPattern:
class TestEdgeCases: class TestEdgeCases:
"""Test edge cases and error handling.""" """Test edge cases and error handling."""
def test_empty_file(self, tmp_path: Path, capsys) -> None: def test_empty_file(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""Should handle empty file.""" """Should handle empty file."""
test_file = tmp_path / "empty.txt" test_file = tmp_path / "empty.txt"
test_file.write_text("", encoding="utf-8") test_file.write_text("", encoding="utf-8")
@@ -280,7 +281,7 @@ class TestBumpVersionCli:
# Mock px.run: 只真正执行第一次调用(版本更新),其余返回空 dict # Mock px.run: 只真正执行第一次调用(版本更新),其余返回空 dict
with patch("sys.argv", ["bumpversion", "minor", "--no-tag"]), patch("pyflowx.run") as mock_run: with patch("sys.argv", ["bumpversion", "minor", "--no-tag"]), patch("pyflowx.run") as mock_run:
def run_side_effect(graph, strategy=None): def run_side_effect(graph: px.Graph, strategy: str | None = None):
# 执行实际版本更新任务 # 执行实际版本更新任务
results = {} results = {}
for spec in graph.specs.values(): for spec in graph.specs.values():
@@ -294,14 +295,14 @@ class TestBumpVersionCli:
# 验证版本号已更新 # 验证版本号已更新
assert test_file.read_text(encoding="utf-8") == '__version__ = "1.1.0"' assert test_file.read_text(encoding="utf-8") == '__version__ = "1.1.0"'
def test_no_valid_files(self, tmp_path: Path, capsys) -> None: def test_no_valid_files(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
"""Should handle no valid files.""" """Should handle no valid files."""
test_file = tmp_path / "test.txt" test_file = tmp_path / "test.txt"
test_file.write_text("这是一个测试文件", encoding="utf-8") test_file.write_text("这是一个测试文件", encoding="utf-8")
with patch("sys.argv", ["bumpversion", "minor", "--no-tag"]), patch("pyflowx.run") as mock_run: with patch("sys.argv", ["bumpversion", "minor", "--no-tag"]), patch("pyflowx.run") as mock_run:
def run_side_effect(graph, strategy=None): def run_side_effect(graph: px.Graph, strategy: str | None = None):
# 执行实际版本更新任务 # 执行实际版本更新任务
results = {} results = {}
for spec in graph.specs.values(): for spec in graph.specs.values():
+36 -57
View File
@@ -30,6 +30,8 @@ class TestEmailDatabase:
db_path = tmp_path / "test.db" db_path = tmp_path / "test.db"
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
assert db.conn is not None
cursor = db.conn.cursor() cursor = db.conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='emails'") cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='emails'")
result = cursor.fetchone() result = cursor.fetchone()
@@ -41,6 +43,8 @@ class TestEmailDatabase:
db_path = tmp_path / "test.db" db_path = tmp_path / "test.db"
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
assert db.conn is not None
cursor = db.conn.cursor() cursor = db.conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_subject'") cursor.execute("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_subject'")
result = cursor.fetchone() result = cursor.fetchone()
@@ -68,6 +72,7 @@ class TestEmailDatabase:
result = db.insert_email(email_data) result = db.insert_email(email_data)
assert result is True assert result is True
assert db.conn is not None
cursor = db.conn.cursor() cursor = db.conn.cursor()
cursor.execute("SELECT COUNT(*) FROM emails") cursor.execute("SELECT COUNT(*) FROM emails")
@@ -101,6 +106,8 @@ class TestEmailDatabase:
email_data["file_hash"] = "xyz789" email_data["file_hash"] = "xyz789"
db.insert_email(email_data) db.insert_email(email_data)
assert db.conn is not None
cursor = db.conn.cursor() cursor = db.conn.cursor()
cursor.execute("SELECT COUNT(*) FROM emails") cursor.execute("SELECT COUNT(*) FROM emails")
count = cursor.fetchone()[0] count = cursor.fetchone()[0]
@@ -118,8 +125,7 @@ class TestEmailDatabase:
# Insert test emails # Insert test emails
for i in range(5): for i in range(5):
db.insert_email( db.insert_email({
{
"file_path": f"/test/path{i}.eml", "file_path": f"/test/path{i}.eml",
"file_hash": f"hash{i}", "file_hash": f"hash{i}",
"subject": f"Subject {i}", "subject": f"Subject {i}",
@@ -131,8 +137,7 @@ class TestEmailDatabase:
"body_html": f"<p>Body {i}</p>", "body_html": f"<p>Body {i}</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
results = db.search_emails(limit=3) results = db.search_emails(limit=3)
assert len(results) == 3 assert len(results) == 3
@@ -143,8 +148,7 @@ class TestEmailDatabase:
db_path = tmp_path / "test.db" db_path = tmp_path / "test.db"
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
db.insert_email( db.insert_email({
{
"file_path": "/test/path1.eml", "file_path": "/test/path1.eml",
"file_hash": "hash1", "file_hash": "hash1",
"subject": "Important Meeting", "subject": "Important Meeting",
@@ -156,11 +160,9 @@ class TestEmailDatabase:
"body_html": "<p>Meeting body</p>", "body_html": "<p>Meeting body</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
db.insert_email( db.insert_email({
{
"file_path": "/test/path2.eml", "file_path": "/test/path2.eml",
"file_hash": "hash2", "file_hash": "hash2",
"subject": "Casual Chat", "subject": "Casual Chat",
@@ -172,8 +174,7 @@ class TestEmailDatabase:
"body_html": "<p>Chat body</p>", "body_html": "<p>Chat body</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
results = db.search_emails(keyword="Meeting", field="subject") results = db.search_emails(keyword="Meeting", field="subject")
assert len(results) == 1 assert len(results) == 1
@@ -185,8 +186,7 @@ class TestEmailDatabase:
db_path = tmp_path / "test.db" db_path = tmp_path / "test.db"
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
db.insert_email( db.insert_email({
{
"file_path": "/test/path1.eml", "file_path": "/test/path1.eml",
"file_hash": "hash1", "file_hash": "hash1",
"subject": "Test", "subject": "Test",
@@ -198,11 +198,9 @@ class TestEmailDatabase:
"body_html": "<p>Body</p>", "body_html": "<p>Body</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
db.insert_email( db.insert_email({
{
"file_path": "/test/path2.eml", "file_path": "/test/path2.eml",
"file_hash": "hash2", "file_hash": "hash2",
"subject": "Test", "subject": "Test",
@@ -214,8 +212,7 @@ class TestEmailDatabase:
"body_html": "<p>Body</p>", "body_html": "<p>Body</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
results = db.search_emails(keyword="alice", field="sender") results = db.search_emails(keyword="alice", field="sender")
assert len(results) == 1 assert len(results) == 1
@@ -227,8 +224,7 @@ class TestEmailDatabase:
db_path = tmp_path / "test.db" db_path = tmp_path / "test.db"
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
db.insert_email( db.insert_email({
{
"file_path": "/test/path1.eml", "file_path": "/test/path1.eml",
"file_hash": "hash1", "file_hash": "hash1",
"subject": "Project Update", "subject": "Project Update",
@@ -240,8 +236,7 @@ class TestEmailDatabase:
"body_html": "<p>Please review the quarterly report</p>", "body_html": "<p>Please review the quarterly report</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
# Search for keyword in subject # Search for keyword in subject
results = db.search_emails(keyword="Project", field="all") results = db.search_emails(keyword="Project", field="all")
@@ -258,8 +253,7 @@ class TestEmailDatabase:
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
# Insert emails with same subject (different prefixes) # Insert emails with same subject (different prefixes)
db.insert_email( db.insert_email({
{
"file_path": "/test/path1.eml", "file_path": "/test/path1.eml",
"file_hash": "hash1", "file_hash": "hash1",
"subject": "Meeting Tomorrow", "subject": "Meeting Tomorrow",
@@ -271,11 +265,9 @@ class TestEmailDatabase:
"body_html": "<p>Body 1</p>", "body_html": "<p>Body 1</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
db.insert_email( db.insert_email({
{
"file_path": "/test/path2.eml", "file_path": "/test/path2.eml",
"file_hash": "hash2", "file_hash": "hash2",
"subject": "Re: Meeting Tomorrow", "subject": "Re: Meeting Tomorrow",
@@ -287,11 +279,9 @@ class TestEmailDatabase:
"body_html": "<p>Body 2</p>", "body_html": "<p>Body 2</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
db.insert_email( db.insert_email({
{
"file_path": "/test/path3.eml", "file_path": "/test/path3.eml",
"file_hash": "hash3", "file_hash": "hash3",
"subject": "Different Topic", "subject": "Different Topic",
@@ -303,8 +293,7 @@ class TestEmailDatabase:
"body_html": "<p>Body 3</p>", "body_html": "<p>Body 3</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
grouped = db.get_grouped_emails() grouped = db.get_grouped_emails()
# Should have 2 groups: "Meeting Tomorrow" and "Different Topic" # Should have 2 groups: "Meeting Tomorrow" and "Different Topic"
@@ -333,8 +322,7 @@ class TestEmailDatabase:
assert db.get_email_count() == 0 assert db.get_email_count() == 0
for i in range(3): for i in range(3):
db.insert_email( db.insert_email({
{
"file_path": f"/test/path{i}.eml", "file_path": f"/test/path{i}.eml",
"file_hash": f"hash{i}", "file_hash": f"hash{i}",
"subject": f"Subject {i}", "subject": f"Subject {i}",
@@ -346,8 +334,7 @@ class TestEmailDatabase:
"body_html": f"<p>Body {i}</p>", "body_html": f"<p>Body {i}</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
assert db.get_email_count() == 3 assert db.get_email_count() == 3
db.close() db.close()
@@ -359,8 +346,7 @@ class TestEmailDatabase:
# Insert some emails # Insert some emails
for i in range(3): for i in range(3):
db.insert_email( db.insert_email({
{
"file_path": f"/test/path{i}.eml", "file_path": f"/test/path{i}.eml",
"file_hash": f"hash{i}", "file_hash": f"hash{i}",
"subject": f"Subject {i}", "subject": f"Subject {i}",
@@ -372,8 +358,7 @@ class TestEmailDatabase:
"body_html": f"<p>Body {i}</p>", "body_html": f"<p>Body {i}</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
assert db.get_email_count() == 3 assert db.get_email_count() == 3
@@ -411,7 +396,7 @@ class TestDecodeMimeWords:
def test_decode_none(self) -> None: def test_decode_none(self) -> None:
"""Should handle None input.""" """Should handle None input."""
result = emlmanager.decode_mime_words(None) result = emlmanager.decode_mime_words("")
assert result == "" assert result == ""
def test_decode_mixed_encoding(self) -> None: def test_decode_mixed_encoding(self) -> None:
@@ -702,8 +687,7 @@ class TestEmlManagerHandler:
# Insert some emails # Insert some emails
for i in range(3): for i in range(3):
db.insert_email( db.insert_email({
{
"file_path": f"/test/path{i}.eml", "file_path": f"/test/path{i}.eml",
"file_hash": f"hash{i}", "file_hash": f"hash{i}",
"subject": f"Subject {i}", "subject": f"Subject {i}",
@@ -715,8 +699,7 @@ class TestEmlManagerHandler:
"body_html": f"<p>Body {i}</p>", "body_html": f"<p>Body {i}</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
# Create a mock handler instance without calling __init__ # Create a mock handler instance without calling __init__
handler = Mock(spec=emlmanager.EmlManagerHandler) handler = Mock(spec=emlmanager.EmlManagerHandler)
@@ -738,8 +721,7 @@ class TestEmlManagerHandler:
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
# Insert test email # Insert test email
db.insert_email( db.insert_email({
{
"file_path": "/test/path.eml", "file_path": "/test/path.eml",
"file_hash": "hash", "file_hash": "hash",
"subject": "Test Subject", "subject": "Test Subject",
@@ -751,8 +733,7 @@ class TestEmlManagerHandler:
"body_html": "<p>Test body</p>", "body_html": "<p>Test body</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
# Create a mock handler instance without calling __init__ # Create a mock handler instance without calling __init__
handler = Mock(spec=emlmanager.EmlManagerHandler) handler = Mock(spec=emlmanager.EmlManagerHandler)
@@ -775,8 +756,7 @@ class TestEmlManagerHandler:
db = emlmanager.EmailDatabase(db_path) db = emlmanager.EmailDatabase(db_path)
# Insert test email # Insert test email
db.insert_email( db.insert_email({
{
"file_path": "/test/path.eml", "file_path": "/test/path.eml",
"file_hash": "hash", "file_hash": "hash",
"subject": "Test Subject", "subject": "Test Subject",
@@ -788,8 +768,7 @@ class TestEmlManagerHandler:
"body_html": "<p>Test body</p>", "body_html": "<p>Test body</p>",
"has_attachments": 0, "has_attachments": 0,
"file_size": 1024, "file_size": 1024,
} })
)
assert db.get_email_count() == 1 assert db.get_email_count() == 1
+1
View File
@@ -48,6 +48,7 @@ class TestSetRustMirror:
def test_set_rust_mirror_unknown_uses_default(self, tmp_path: Path) -> None: def test_set_rust_mirror_unknown_uses_default(self, tmp_path: Path) -> None:
"""Should use default mirror for unknown mirror name.""" """Should use default mirror for unknown mirror name."""
with patch.object(Path, "home", return_value=tmp_path): with patch.object(Path, "home", return_value=tmp_path):
# pyrefly: ignore [bad-argument-type]
envrs.set_rust_mirror("unknown") envrs.set_rust_mirror("unknown")
# Should use default mirror (tsinghua) # Should use default mirror (tsinghua)
assert os.environ.get("RUSTUP_DIST_SERVER") == "https://mirrors.tuna.tsinghua.edu.cn/rustup" assert os.environ.get("RUSTUP_DIST_SERVER") == "https://mirrors.tuna.tsinghua.edu.cn/rustup"
+1
View File
@@ -107,6 +107,7 @@ class TestTaskSpecDefinitions:
def test_kill_tgit_spec(self) -> None: def test_kill_tgit_spec(self) -> None:
"""kill_tgit spec should be properly defined.""" """kill_tgit spec should be properly defined."""
assert gittool.kill_tgit.name == "task_kill" assert gittool.kill_tgit.name == "task_kill"
assert isinstance(gittool.kill_tgit.cmd, list)
assert "taskkill" in gittool.kill_tgit.cmd assert "taskkill" in gittool.kill_tgit.cmd
+2 -1
View File
@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
@@ -71,7 +72,7 @@ class TestPdfCompress:
mock_fitz_open.return_value = mock_doc mock_fitz_open.return_value = mock_doc
# Mock save to actually create the file # Mock save to actually create the file
def mock_save(*args, **kwargs): def mock_save(*args: Any, **kwargs: Any):
output_file.write_bytes(b"Compressed PDF") output_file.write_bytes(b"Compressed PDF")
mock_doc.save = mock_save mock_doc.save = mock_save
+4 -4
View File
@@ -54,7 +54,7 @@ def test_verbose_event_callback_running():
assert report.success assert report.success
def test_verbose_run_with_success_lifecycle(capsys): def test_verbose_run_with_success_lifecycle(capsys: pytest.CaptureFixture[str]):
"""Test px.run with verbose=True prints SUCCESS lifecycle.""" """Test px.run with verbose=True prints SUCCESS lifecycle."""
spec = px.TaskSpec("test", fn=lambda: "result") spec = px.TaskSpec("test", fn=lambda: "result")
graph = px.Graph.from_specs([spec]) graph = px.Graph.from_specs([spec])
@@ -64,7 +64,7 @@ def test_verbose_run_with_success_lifecycle(capsys):
assert "成功" in captured.out assert "成功" in captured.out
def test_verbose_run_with_failed_lifecycle(capsys): def test_verbose_run_with_failed_lifecycle(capsys: pytest.CaptureFixture[str]):
"""Test px.run with verbose=True prints FAILED lifecycle with error.""" """Test px.run with verbose=True prints FAILED lifecycle with error."""
def raise_error(): def raise_error():
@@ -80,7 +80,7 @@ def test_verbose_run_with_failed_lifecycle(capsys):
assert "test error" in captured.out assert "test error" in captured.out
def test_verbose_run_with_skipped_lifecycle(capsys): def test_verbose_run_with_skipped_lifecycle(capsys: pytest.CaptureFixture[str]):
"""Test px.run with verbose=True prints SKIPPED lifecycle.""" """Test px.run with verbose=True prints SKIPPED lifecycle."""
spec = px.TaskSpec( spec = px.TaskSpec(
"test", "test",
@@ -98,7 +98,7 @@ def test_verbose_run_with_user_callback():
"""Test px.run with verbose=True and user callback both called.""" """Test px.run with verbose=True and user callback both called."""
events = [] events = []
def on_event(event): def on_event(event: px.TaskEvent):
events.append(event) events.append(event)
spec = px.TaskSpec("test", fn=lambda: "result") spec = px.TaskSpec("test", fn=lambda: "result")
+1 -1
View File
@@ -177,7 +177,7 @@ def test_taskspec_shell_cmd_file_not_found_mocked():
_ = wrapped_fn() _ = wrapped_fn()
def test_taskspec_shell_cmd_with_cwd_verbose(capsys): def test_taskspec_shell_cmd_with_cwd_verbose(capsys: pytest.CaptureFixture[str]):
"""Test TaskSpec._wrap_cmd with shell command, cwd and verbose=True.""" """Test TaskSpec._wrap_cmd with shell command, cwd and verbose=True."""
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
if sys.platform == "win32": if sys.platform == "win32":
+4 -4
View File
@@ -11,7 +11,7 @@ _NODE_DONE = ...
class _NodeInfo: class _NodeInfo:
__slots__: list[str] __slots__: list[str]
def __init__(self, node) -> None: ... def __init__(self, node: Any) -> None: ...
class CycleError(ValueError): class CycleError(ValueError):
"""Subclass of ValueError raised by TopologicalSorterif cycles exist in the graph """Subclass of ValueError raised by TopologicalSorterif cycles exist in the graph
@@ -29,8 +29,8 @@ class CycleError(ValueError):
class TopologicalSorter: class TopologicalSorter:
"""Provides functionality to topologically sort a graph of hashable nodes""" """Provides functionality to topologically sort a graph of hashable nodes"""
def __init__(self, graph=...) -> None: ... def __init__(self, graph: Any) -> None: ...
def add(self, node, *predecessors) -> None: def add(self, node: Any, *predecessors: Any) -> None:
"""Add a new node and its predecessors to the graph. """Add a new node and its predecessors to the graph.
Both the *node* and all elements in *predecessors* must be hashable. Both the *node* and all elements in *predecessors* must be hashable.
@@ -86,7 +86,7 @@ class TopologicalSorter:
... ...
def __bool__(self) -> bool: ... def __bool__(self) -> bool: ...
def done(self, *nodes) -> None: def done(self, *nodes: Any) -> None:
"""Marks a set of nodes returned by "get_ready" as processed. """Marks a set of nodes returned by "get_ready" as processed.
This method unblocks any successor of each node in *nodes* for being returned This method unblocks any successor of each node in *nodes* for being returned