Files
chenmingxuan be6e8c2abd feat: release 2.2.0 with PC-only tests and execution reliability fixes
Unify task scheduling and queue state, fix Android input and observation recovery, preserve screenshot paths, and match final review criteria accurately. Add collapsible copyable results and publish the complete 2.1.0 upgrade notes. Validation: 3189 backend tests, 326 frontend tests, 24 client tests, production build and quality gates.
2026-10-27 01:13:41 +08:00

243 lines
9.0 KiB
Python

# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from artemis.utils.notes import (
append_note_content,
list_notes_info,
read_note_content,
save_note_content,
save_verified_note,
update_note_content,
)
import pytest
@pytest.mark.parametrize("operation", ["save", "append", "update"])
@pytest.mark.parametrize("path_style", ["native", "posix"])
def test_note_rejects_misplaced_artifact_and_accepts_exact_receipt(tmp_path, operation, path_style):
artifact = tmp_path / "artifacts" / "android" / "截图 one.png"
artifact.parent.mkdir(parents=True)
artifact.write_bytes(b"evidence")
save_note_content(tmp_path, "evidence", "original")
note = tmp_path / "notes" / "evidence.md"
before = note.read_bytes()
missing = tmp_path / artifact.name
wrong = str(missing) if path_style == "native" else missing.as_posix()
def write(content):
if operation == "save":
save_note_content(tmp_path, "evidence", content)
elif operation == "append":
append_note_content(tmp_path, "evidence", content)
else:
update_note_content(tmp_path, "evidence", "original", content)
with pytest.raises(ValueError, match="exact trace_path") as error:
write(f"Screenshot: `{wrong}`。")
assert str(artifact) in str(error.value)
assert note.read_bytes() == before
assert artifact.read_bytes() == b"evidence"
write(f"Screenshot: `{artifact}`")
assert str(artifact) in note.read_text(encoding="utf-8")
def test_note_does_not_guess_between_duplicate_artifact_names(tmp_path):
files = [
tmp_path / "artifacts" / "android" / "screen.png",
tmp_path / "artifacts" / "plugins" / "cua-driver" / "screen.png",
]
for path in files:
path.parent.mkdir(parents=True)
path.write_bytes(b"evidence")
with pytest.raises(ValueError) as error:
save_note_content(tmp_path, "evidence", str(tmp_path / "screen.png"))
assert all(str(path) in str(error.value) for path in files)
assert not (tmp_path / "notes" / "evidence.md").exists()
def test_note_allows_existing_root_files_and_unknown_future_paths(tmp_path):
artifact = tmp_path / "artifacts" / "android" / "screen.png"
artifact.parent.mkdir(parents=True)
artifact.write_bytes(b"artifact")
root_file = tmp_path / "screen.png"
root_file.write_bytes(b"different root file")
content = f"{root_file}\n{tmp_path / 'future.png'}\n{tmp_path / 'screen.png.backup'}"
save_note_content(tmp_path, "plan", content)
assert read_note_content(tmp_path, "plan") == content
@pytest.mark.parametrize("content", ["# 报告\n结果\n", "line\r\nsecond", ""])
def test_verified_note_receipt_matches_actual_file_bytes(tmp_path, content):
import hashlib
receipt = save_verified_note(tmp_path, "report.md", content)
data = (tmp_path / receipt["path"]).read_bytes()
assert receipt["path"] == "notes/report.md"
assert receipt["verified"] is True and receipt["bytes"] == len(data)
assert receipt["sha256"] == hashlib.sha256(data).hexdigest()
def test_verified_note_does_not_confirm_incomplete_write(tmp_path, monkeypatch):
from artemis.utils import notes
def partial_write(base_dir, key, content):
save_note_content(base_dir, key, content[:1])
monkeypatch.setattr(notes, "save_note_content", partial_write)
with pytest.raises(OSError, match="读回不一致"):
save_verified_note(tmp_path, "report", "完整报告")
def test_list_notes_info(tmp_path):
base_dir = tmp_path
# Create some notes
save_note_content(base_dir, "note1", "line1\nline2\nline3")
save_note_content(base_dir, "note2", "line1\nline2")
info = list_notes_info(base_dir)
assert info == {"note1": 3, "note2": 2}
def test_read_note_content_range(tmp_path):
base_dir = tmp_path
save_note_content(base_dir, "note1", "line1\nline2\nline3\nline4")
# Read full
assert read_note_content(base_dir, "note1") == "line1\nline2\nline3\nline4"
# Read range
assert read_note_content(base_dir, "note1", start_line=2, end_line=3) == "line2\nline3"
# Read from start to line
assert read_note_content(base_dir, "note1", end_line=2) == "line1\nline2"
# Read from line to end
assert read_note_content(base_dir, "note1", start_line=3) == "line3\nline4"
def test_update_note_exact_match(tmp_path):
base_dir = tmp_path
original_content = 'def demo():\n print("Hello, World!")\n return True\n'
save_note_content(base_dir, "task_plan", original_content)
# Exact match
warning = update_note_content(
base_dir,
"task_plan",
' print("Hello, World!")',
' print("Hello, Jetski!")',
)
assert warning is None
updated_content = read_note_content(base_dir, "task_plan")
assert 'print("Hello, Jetski!")' in updated_content
assert 'print("Hello, World!")' not in updated_content
def test_update_note_relaxed_match(tmp_path):
base_dir = tmp_path
# Multi-line content with checkboxes and indentation
original_content = (
"- [ ] Step 1: Research the issue\n - [ ] Step 2: Write code\n - [ ] Step 3: Run tests\n"
)
save_note_content(base_dir, "task_plan", original_content)
# Relaxed match: case difference, quote difference, checkbox bullet difference, trailing whitespace
target = " * [ ] step 2: write code "
replacement = " - [/] Step 2: In progress"
warning = update_note_content(base_dir, "task_plan", target, replacement)
# Warning should be returned
assert warning is not None
assert "We did our best to apply changes despite some inaccuracies" in warning
updated_content = read_note_content(base_dir, "task_plan")
assert "Step 2: In progress" in updated_content
assert "Step 2: Write code" not in updated_content
# Check indentation is preserved from replacement
assert " - [/] Step 2: In progress\n" in updated_content
def test_update_note_fuzzy_match(tmp_path):
base_dir = tmp_path
original_content = 'print("Alpha message")\nprint("Gamma message")\nprint("Delta message")\n'
save_note_content(base_dir, "task_plan", original_content)
# Fuzzy match: typo (Gemma vs Gamma)
target = 'print("Gemma message")'
replacement = 'print("Gamma updated!")'
warning = update_note_content(base_dir, "task_plan", target, replacement)
assert warning is not None
assert "We did our best to apply changes despite some inaccuracies" in warning
updated_content = read_note_content(base_dir, "task_plan")
assert 'print("Gamma updated!")' in updated_content
assert 'print("Gamma message")' not in updated_content
def test_update_note_ambiguous_match(tmp_path):
base_dir = tmp_path
# Multiple lines that could match target
original_content = 'print("Beta message")\nprint("Zeta message")\nprint("Eta message")\n'
save_note_content(base_dir, "task_plan", original_content)
# Target is highly ambiguous (Xeta is almost equally close to Beta and Zeta)
target = 'print("Xeta message")'
replacement = 'print("Ambiguous match!")'
# Should raise ValueError due to ambiguity or inability to find unique match
with pytest.raises(ValueError) as exc_info:
update_note_content(base_dir, "task_plan", target, replacement)
assert "Target string 'print(\"Xeta message\")' not found" in str(
exc_info.value
) or "not unique" in str(exc_info.value)
def test_update_note_not_found(tmp_path):
base_dir = tmp_path
original_content = 'print("Alpha message")\n'
save_note_content(base_dir, "task_plan", original_content)
# Totally different target
target = 'print("Something totally different")'
replacement = 'print("Hello")'
with pytest.raises(ValueError) as exc_info:
update_note_content(base_dir, "task_plan", target, replacement)
assert "not found" in str(exc_info.value)
def test_append_note_content(tmp_path):
base_dir = tmp_path
# Case 1: File does not exist
append_note_content(base_dir, "new_note", "hello")
assert read_note_content(base_dir, "new_note") == "hello\n"
# Case 2: File exists and ends with a newline
append_note_content(base_dir, "new_note", "world")
assert read_note_content(base_dir, "new_note") == "hello\nworld\n"
# Case 3: File exists and does not end with a newline
file_path = base_dir / "notes" / "no_newline.md"
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_text("no newline", encoding="utf-8")
append_note_content(base_dir, "no_newline", "added text")
assert read_note_content(base_dir, "no_newline") == "no newline\nadded text\n"