make the coverage harness concurrency-safe (T-345)
The two coverage passes shared fixed paths (coverage/lcov.info → lcov.parallel.info → merge → rm -f), so a concurrent flutter test --coverage — a second push gate, or a `make test` during a push — raced and deleted this run's intermediate mid-merge, crashing merge_lcov with FileNotFoundError. Each pass now writes to a per-run mktemp dir via --coverage-path; the merge runs from there and only the final result lands in coverage/lcov.info via an atomic rename within coverage/. No shared intermediates, so concurrent runs can't corrupt each other. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3502,3 +3502,8 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'status', 'in_progress', 'done', NULL, '2026-06-10 16:12:15', '2026-06-10 16:12:15', '2026-06-10 16:12:15', NULL, '4cb2daa831a115888a9456461bf53bdf', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4J5E6W983P1S7BE0FDPSMM', 'status', 'in_progress', 'in_progress', NULL, '2026-06-10 16:13:50', '2026-06-10 16:13:50', '2026-06-10 16:13:50', NULL, '88bf89e802bd16209db6d2c0a9b963c0', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4J5E6W983P1S7BE0FDPSMM', 'status', 'in_progress', 'done', NULL, '2026-06-10 16:15:38', '2026-06-10 16:15:38', '2026-06-10 16:15:38', NULL, '4aa30d9c68463d18f5778cbd20c9da5a', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB493JEW32CH0H3771TNHF7G', 'status', 'backlog', 'ready', NULL, '2026-06-10 16:21:55', '2026-06-10 16:21:55', '2026-06-10 16:21:55', NULL, 'b2f20ad3cb093b7e89342f6b374097ff', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB44SKPKTHFMV6WD28GZYPXM', 'status', 'backlog', 'ready', NULL, '2026-06-10 16:22:24', '2026-06-10 16:22:24', '2026-06-10 16:22:24', NULL, 'b88b0e1ef2e1c3711527388dc8d07cce', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DWCJSGZH9WYDNFWZBAYYR', 'status', 'backlog', 'ready', NULL, '2026-06-10 16:22:28', '2026-06-10 16:22:28', '2026-06-10 16:22:28', NULL, '4b58326ae217b60be81a07603afee36d', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2T11GCV1EV07DYD5BZENTM', 'status', 'backlog', 'ready', NULL, '2026-06-10 16:22:37', '2026-06-10 16:22:37', '2026-06-10 16:22:37', NULL, 'c62c5ac18a88ba763b5194f89a2a7482', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4RD1DDSYM4J7WYEGTXARB4', 'status', 'backlog', 'done', NULL, '2026-06-10 16:34:51', '2026-06-10 16:34:51', '2026-06-10 16:34:51', NULL, '56cde33409eb796ad4fb420ed3033c70', 2) ON CONFLICT(hash) DO NOTHING;
|
||||
|
||||
@@ -3096,3 +3096,81 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
|
||||
Result: an ''Activity Bash …'' collapser and a neighbouring ''Bash · denied'' tool card render their label + collapsed summary at different sizes in the same list. Spotted in a screenshot of three stacked cards (collapser Bash, denied tool result, collapser Read).
|
||||
|
||||
Fix: pick one set of tokens for the shared header/label/summary role across both primitives (likely align ConversationCard''s label to clideFontCaption and standardise the collapsed-summary size), or extract a shared card-header style. Tokens: clideFontCaption=14, clideFontMeta=13, clideFontSmall=12 (lib/widgets/src/typography.dart).', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 16:06:08', '2026-06-10 16:15:38', NULL, 'e5e9f526180b6e48628629c2c3c5138b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB493JEW32CH0H3771TNHF7G', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Give each spawned subagent its own collapsing activity card (don''t merge Task spawns)', 'Filed 2026-06-10 from a user request: when the agent fans out multiple subagents (Task/Agent tool), each spawned subagent should get its OWN collapsing activity card. The space is worth it — a fan-out of N agents should read as N cards, not one lumped card.
|
||||
|
||||
CURRENT BEHAVIOUR (confirmed): multiple Task/Agent spawns are MERGED into a single shared ''Activity / N steps'' cluster. groupConversation() in lib/builtin/claude/src/activity_cluster.dart:120-147 walks items and coalesces every consecutive _isFoldable item into one FoldedCluster. _isFoldable (:149-172, AssistantToolUse case at :161-164) only distinguishes diff tools (Edit/Write/MultiEdit/NotebookEdit/Update -> stay first-class) from everything else (Task/Agent/Bash/Read/... -> all foldable). The Task tool is treated identically to a Bash/Read call; there is NO subagent-aware grouping key (not toolUseId, not parent_tool_use_id). So 4 spawned agents render as one ''Activity 4 steps'' card.
|
||||
|
||||
What recent work already does (do NOT redo): T-263 folds the subagent PROMPT into the Agent card; T-264 nests the subagent''s RUN items under the parent Agent card (the ''agent run'' collapser); T-338 routes sidechain items to their parent via parent_tool_use_id. All of that is about what shows INSIDE one agent''s card. This ticket is the complement: stop merging DISTINCT agent spawns into a shared cluster.
|
||||
|
||||
SCOPE / DESIGN:
|
||||
- An AssistantToolUse where _isAgentTool(name) (Task/Agent) should break the current Activity cluster and render as its own first-class collapsing card (its own ClideCollapserCard with the prompt + nested ''agent run'' from T-263/T-264), rather than folding into the generic Activity cluster with sibling tool calls.
|
||||
- Decide grouping precisely in groupConversation/_isFoldable: an Agent tool-use is a cluster boundary (like a sticky item) OR emits its own single-item card. Adjacent non-agent foldables (Bash/Read/Grep) keep clustering into the normal Activity card as today.
|
||||
- Label each subagent card by its task/description (the Agent call''s label) so parallel fan-outs are distinguishable, not ''Activity N steps''.
|
||||
- Keep collapsed-by-default behaviour and the FoldLevel semantics; this changes the grouping boundary, not the fold mechanics.
|
||||
|
||||
Refs: lib/builtin/claude/src/activity_cluster.dart (groupConversation, _isFoldable, isDiffTool), lib/builtin/claude/src/conversation_view.dart (_ActivityCard at ~833, _toolUseCollapser ~615-657, _isAgentTool ~378). Related: T-230 (clustering), T-263, T-264, T-338.
|
||||
|
||||
Tests: a groupConversation case asserting that two consecutive Agent tool-uses yield two separate cards (not one FoldedCluster), while two consecutive Bash calls still yield one Activity cluster.
|
||||
|
||||
SCOPE CLARIFICATION (2026-06-10, from user):
|
||||
|
||||
1. RIGHT card, not just A card. Each per-agent card must pull in ALL of that agent''s nested run — the folded prompt (T-263) AND every nested response: prose, thinking, sidechain tool cards, and results (T-264) — attributed to the CORRECT agent even under a parallel fan-out where multiple agents'' sidechain items interleave in the stream. Reuse the existing _sidechainFold machinery (conversation_view.dart:188-256): runByToolUseId / promptsByToolUseId are already keyed by the owning Agent''s toolUseId, and resolveOwner''s direct route (conversation_view.dart:210-217) uses parent_tool_use_id (T-338) which disambiguates concurrent agents correctly. THE HAZARD: resolveOwner falls back to ''nearest'' = lastAgent (the most-recently-emitted Agent in stream order; conversation_view.dart:228, applied at :242). In a parallel fan-out any item that lacks parent_tool_use_id and a rooted parentUuid chain would mis-route to whichever agent was emitted last — landing in the WRONG card. Harden this: for the multi-agent case, drop or guard the nearest-lastAgent fallback so an unattributable item is rendered inline/orphaned (resolveOwner already returns null -> handled at :243) rather than mis-filed into a sibling agent''s card.
|
||||
|
||||
2. PRESERVE the existing grouping. This ticket only adds an Agent-spawn cluster boundary; it must NOT regress the rest:
|
||||
- Non-agent foldables (Bash/Read/Grep/LS/etc.) keep coalescing into the generic ''Activity / N steps'' cluster exactly as today (activity_cluster.dart groupConversation/_isFoldable).
|
||||
- The intra-agent folding stays: prompt-into-call (T-263), run-nested-under-card (T-264), sidechain routing by parent_tool_use_id (T-338). Reuse them; do not rebuild.
|
||||
- Net behaviour: a fan-out of N agents -> N distinct collapsed cards, each containing its own complete run; surrounding non-agent tool calls still group into their normal Activity card.
|
||||
|
||||
Test additions: (a) two concurrent agents whose sidechain items interleave -> each agent''s run items land under its own card, none cross-attributed; (b) an unattributable sidechain item (no parent_tool_use_id, broken chain) is NOT swept into the nearest agent''s card; (c) regression: consecutive Bash/Read calls still form one Activity cluster.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-10 15:26:33', '2026-06-10 16:21:55', NULL, 'dcbfa661a4a0238a2578ee8ed800444b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB44SKPKTHFMV6WD28GZYPXM', 'task', NULL, 'Support double-tap / bare-modifier chords (JetBrains Search Everywhere)', 'The KeyChord matcher can''t represent a bare or double-tapped modifier: KeyChord.fromKeyEvent returns null for bare modifier presses (key_chord.dart _isBareModifier), and KeyChord.parse requires a base key after the last ''+'', so ''shift+shift'' / ''shift shift'' both fail. Consequence: JetBrains ''Search Everywhere'' (double-Shift) and any double-tap-modifier shortcut can''t be bound.
|
||||
|
||||
Decide (design): is search-everywhere its own intent + overlay, or an alias of quick-open? Then extend the chord/sequence matcher to represent a bare-modifier ''key'' (and a double-tap as a sequence) so a preset can bind it.
|
||||
|
||||
Surfaced by T-64/T-66 (VS Code / JetBrains keymap presets). Those tickets'' ''see Q-9'' reference is STALE — Q-9 is ''Lua runtime vendoring'', unrelated. The JetBrains preset currently maps quick-open to Ctrl+Shift+N (Go to File) and the palette to Ctrl+Shift+A (Find Action) as the expressible IntelliJ equivalents.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-10 15:07:43', '2026-06-10 16:22:24', NULL, '1ecfc619afc1c684d10ccce3f2ad2a72', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB3DWCJSGZH9WYDNFWZBAYYR', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'ExitPlanMode does not exit plan mode in the conversation panel', 'Observed live: Claude used the ExitPlanMode tool (approved the plan / left plan mode), but the clide conversation panel did not reflect the exit — it still appears to be in plan mode afterward.
|
||||
|
||||
Expected: once ExitPlanMode is accepted, the pane leaves plan mode — the permission-mode indicator (claude_status / permission_mode_control) flips off ''plan'', and the composer / status reflect the new mode.
|
||||
|
||||
Where to look:
|
||||
- How the stream-json session handles the ExitPlanMode tool/control event (stream_json_session.dart) and whether it updates the tracked permission mode (claude_status.dart permissionMode).
|
||||
- The permission-mode control + status badge wiring (permission_mode_control.dart, claude_pane.dart) — does an ExitPlanMode resolution feed back into the mode the way Ctrl/Cmd+M and the picker do?
|
||||
- Whether ExitPlanMode arrives as a permission prompt (ToolPromptCard) and, if approved, actually transitions the session out of plan mode rather than just resolving the tool.
|
||||
|
||||
Repro: enter plan mode, let Claude present a plan via ExitPlanMode, accept it — the pane stays showing plan mode.
|
||||
|
||||
Acceptance: accepting ExitPlanMode leaves plan mode in the conversation panel (mode indicator + behavior update), matching what the underlying session does.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-10 13:27:36', '2026-06-10 16:22:28', NULL, 'd87a50140fc2477aad77f2bf10c64da9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2T11GCV1EV07DYD5BZENTM', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Live tail sub-card inside expanded Bash activity cards', 'Expanded Bash activity cards should be able to show a live, scrolling tail of a file-backed source the command is following, so the user can watch the same output Claude is tailing — connected lazily, only while the card is expanded.
|
||||
|
||||
**Motivation**
|
||||
Long-running / tailing Bash steps render only a final result block once the command finishes (see screenshot in T-304 thread). The user can''t follow progress. We want an embedded scrolling terminal sub-card inside the expanded activity card for visibility.
|
||||
|
||||
**Hard constraint (feasibility, established by investigation)**
|
||||
Claude Code CLI executes every Bash tool itself; clide only ingests stream-json events (stream_json_session.dart -> parseTranscriptChunk). A tool_result arrives as ONE complete block — clide never sees in-flight tool output, and the controller''s in-place upsert path is hardcoded for `partial-<msg_id>` prose deltas, not tool results (controller.dart). We cannot tap Claude''s subprocess fd. So a fully-general "mirror the running command''s stdout" is NOT possible.
|
||||
|
||||
**Viable scope: independent file-tail on expand**
|
||||
- On card expand, parse the Bash command string (AssistantToolUse.input["command"], transcript_reader.dart) for a file-backed source: `tail -f X`, `tail -n N X`, `cat X`, `less X`, known log paths, or a background task writing to a file.
|
||||
- If found, clide opens its OWN read-only follower — a NativePty running `tail -f X` (native_pty.dart already exposes Stream<Uint8List>) or a Dart file-watcher — and renders it in an embedded scrolling terminal sub-card, reusing TerminalView.
|
||||
- Lazy lifecycle: connect on expand, disconnect/dispose on collapse. No connection until expanded.
|
||||
- Read-only & safe: clide observes the same file; it never re-runs or intercepts Claude''s command.
|
||||
- Graceful when there is no file-backed source (e.g. `git push ... | tail -25`, a pipe inside Claude''s process): show a muted "no independent source to follow" note rather than faking output.
|
||||
|
||||
**Where**
|
||||
- Card body: conversation_card.dart / conversation_view.dart (_toolUseCollapser, the Bash tool card). Add an optional live-tail segment below the RESULT segment.
|
||||
- Key the live connection on AssistantToolUse.toolUseId.
|
||||
- Embedded terminal: reuse lib/src/terminal TerminalView + lib/src/pty NativePty.
|
||||
|
||||
**Open questions (settle in review / may need a Q-record)**
|
||||
- Command-parsing surface: which commands/forms count as "tailable"? Keep it a small, explicit allowlist to avoid mis-following.
|
||||
- Security: only follow paths inside the workspace? How to handle absolute paths outside the repo.
|
||||
- Lifecycle when the underlying command has already finished (file static) vs still running.
|
||||
|
||||
**Acceptance**
|
||||
- Expanding a Bash card whose command tails a file shows a live scrolling terminal sub-card that updates as the file grows; collapsing it tears the follower down.
|
||||
- A Bash card with no file-backed source shows a clear "nothing to follow" affordance, never a broken/empty terminal.
|
||||
- No follower process is spawned until the card is expanded.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-10 12:00:52', '2026-06-10 16:22:37', NULL, '031ad585f4bea545d6efa0ead8c3830e', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4RD1DDSYM4J7WYEGTXARB4', 'task', NULL, 'Make the coverage harness concurrency-safe (per-run paths)', 'ci/test.sh --coverage runs two flutter test --coverage passes that both write the fixed coverage/lcov.info, copies it to coverage/lcov.parallel.info, merges, then rm -f''s the intermediate. Any concurrent flutter test --coverage (a second push gate, or a ''make test'' during a push) races on those shared paths — the second run''s rm -f deletes lcov.parallel.info mid-merge, crashing merge_lcov.py with FileNotFoundError (observed during a push, 2026-06-10).
|
||||
|
||||
Fix: give each run its own coverage output via flutter test --coverage-path <tmp>, with a per-run mktemp dir for the two passes + the merge; only the final merged result lands in coverage/lcov.info via an atomic rename within coverage/. No shared intermediate paths, so concurrent runs can''t corrupt each other.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-10 16:33:23', '2026-06-10 16:33:23', NULL, '9b0de784a50a9ba162dc46fc477ca6b5', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4RD1DDSYM4J7WYEGTXARB4', 'task', NULL, 'Make the coverage harness concurrency-safe (per-run paths)', 'ci/test.sh --coverage runs two flutter test --coverage passes that both write the fixed coverage/lcov.info, copies it to coverage/lcov.parallel.info, merges, then rm -f''s the intermediate. Any concurrent flutter test --coverage (a second push gate, or a ''make test'' during a push) races on those shared paths — the second run''s rm -f deletes lcov.parallel.info mid-merge, crashing merge_lcov.py with FileNotFoundError (observed during a push, 2026-06-10).
|
||||
|
||||
Fix: give each run its own coverage output via flutter test --coverage-path <tmp>, with a per-run mktemp dir for the two passes + the merge; only the final merged result lands in coverage/lcov.info via an atomic rename within coverage/. No shared intermediate paths, so concurrent runs can''t corrupt each other.', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 16:33:23', '2026-06-10 16:34:51', NULL, '75938b68f10edf857e25100141d08544', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||
|
||||
+14
-6
@@ -41,15 +41,23 @@ dart test -r "$REPORTER" --concurrency=1 --tags pty test/pty/session_test.dart t
|
||||
# serial-tagged tests (concurrency-vulnerable — run in their own --concurrency=1
|
||||
# pass below). See dart_test.yaml + T-193.
|
||||
if [[ "$coverage" == 1 ]]; then
|
||||
# Each pass writes its raw coverage into a per-run temp dir (via
|
||||
# --coverage-path), never the shared coverage/lcov.info / lcov.parallel.info.
|
||||
# So a concurrent `flutter test --coverage` — a second push gate, or a
|
||||
# `make test` during a push — can't race or delete this run's intermediates
|
||||
# (which crashed merge_lcov with FileNotFoundError). Only the final merged
|
||||
# result lands in coverage/lcov.info, via an atomic rename within coverage/.
|
||||
# (T-345)
|
||||
COV_TMP="$(mktemp -d "${TMPDIR:-/tmp}/clide-cov.XXXXXX")"
|
||||
trap 'rm -rf "$COV_TMP" "coverage/.lcov.$$.info"' EXIT
|
||||
echo "==> flutter test --coverage (parallel pool; excludes pty + serial)"
|
||||
flutter test -r "$REPORTER" --coverage --exclude-tags "pty || serial" --timeout 60s
|
||||
cp coverage/lcov.info coverage/lcov.parallel.info
|
||||
flutter test -r "$REPORTER" --coverage --coverage-path "$COV_TMP/parallel.info" --exclude-tags "pty || serial" --timeout 60s
|
||||
echo "==> flutter test --coverage (serial-tagged; --concurrency=1)"
|
||||
flutter test -r "$REPORTER" --coverage --tags serial --concurrency=1 --timeout 60s
|
||||
flutter test -r "$REPORTER" --coverage --coverage-path "$COV_TMP/serial.info" --tags serial --concurrency=1 --timeout 60s
|
||||
echo "==> merge coverage (parallel + serial passes → coverage/lcov.info)"
|
||||
python3 ci/merge_lcov.py coverage/lcov.parallel.info coverage/lcov.info > coverage/lcov.merged.info
|
||||
mv coverage/lcov.merged.info coverage/lcov.info
|
||||
rm -f coverage/lcov.parallel.info
|
||||
mkdir -p coverage
|
||||
python3 ci/merge_lcov.py "$COV_TMP/parallel.info" "$COV_TMP/serial.info" > "coverage/.lcov.$$.info"
|
||||
mv -f "coverage/.lcov.$$.info" coverage/lcov.info
|
||||
else
|
||||
echo "==> flutter test (dev; parallel pool, excludes pty + serial)"
|
||||
flutter test -r "$REPORTER" --exclude-tags "pty || serial" --concurrency=12 --timeout 60s
|
||||
|
||||
Reference in New Issue
Block a user