Skip to content

test: 残る 264 箇所の期限ポーリングを settle(_:until:) に移行する(#347 フォローアップ) #349

Description

@GeneralD

agent type scope size production relates

Issue #347PR #348)のフォローアップ。

背景

Issue #347 で、AppRouterTests の期限ポーリングが flaky の原因そのものだと分かった: ContinuousClock 期限付きの Task.sleep ループは、CI が main queue を詰まらせると 期限切れの継続が presenter の .receive(on: DispatchQueue.main) ブロックより先に走り、値が届く直前の状態を assert する。期限を伸ばしても同じ賭けの掛け金が上がるだけ。PR #348 は待つ対象を「subject が書く @Published の値そのもの」に変え、settle(_:until:)$property.valuesfor await)で決定化した。

同じ形のポーリングが他のテストターゲットにそのまま残っている。Tests/PresentersTests/TestSupport/WaitUntil.swift同じ flake 理由を注釈に書いて 2s → 5s に伸ばした実績があり、次に落ちるのはここ。

棚卸し

Tests/ 配下の待機箇所を 1 ファイル 1 agent で全件列挙し、各箇所について 条件が読む状態の宣言(@Published か / どの isolation が書くか)を Sources/ まで追って 分類。別 agent が反証レビュー(11 件の根拠・行番号を訂正、分類の変更なし)。

  • 対象: 28 ファイル / 264 箇所AppRouterTests は移行済みで除外)
  • スイープで scout リスト外の PresentersTests/ConfigStatusPresenterTests.swiftwaitUntil 3 箇所、未分類 — ConfigStatusPresenter@Published を待つ形なので published 見込み)を追加CompletionCommandTests / YouTubeToolDetectionTestsProcess.waitUntilExit() と、TrackInteractorRaceTests の continuation ベース waitFor対象外(後者は既に正しい形で、§2 の手本)
分類 件数 意味
published 150 settle にそのまま置換可(@Published を await)
spy-readable 54 spy / fake が publish すれば置換可
external 43 外部要因(subprocess / FS / DispatchSource / tick 駆動)— 期限ポーリングのまま
negative 13 否定アサーション(起きないことの確認)— 別の表現が要る
clock 1 本来は clock 注入で解くべき
fixed-delay 3 条件なしの固定 sleep
ファイル 箇所 published spy external negative clock fixed
AsyncRunnableCommandTests/AsyncRunnableCommandTests.swift 1 1
ConfigDataSourceTests/ConfigWatchTests.swift 5 5
ConfigHotReloadTests/ConfigHotReloadPipelineTests.swift 3 3
ConfigInteractorTests/ConfigInteractorImplTests.swift 3 2 1
DarwinGatewayTests/DarwinGatewayProcessTests.swift 5 5
DarwinGatewayTests/DarwinGatewayRunProcessTests.swift 4 4
DarwinGatewayTests/ProcessLockTests.swift 13 13
FileWatchGatewayTests/FileWatchGatewayTests.swift 3 3
LyricsRepositoryTests/LyricsCandidateWaveTests.swift 3 2 1
MediaRemoteDataSourceTests/MediaRemoteDataSourceImplTests.swift 1 1
NowPlayingRepositoryTests/NowPlayingRepositoryTests.swift 1 1
PresentersTests/AppPresenterTests.swift 17 14 2 1
PresentersTests/HeaderPresenterDuplicateTests.swift 7 5 2
PresentersTests/HeaderPresenterTests.swift 14 13 1
PresentersTests/LyricsPresenterColumnsTests.swift 4 4
PresentersTests/LyricsPresenterDuplicateTests.swift 8 3 3 2
PresentersTests/LyricsPresenterTests.swift 26 15 7 4
PresentersTests/SpectrumPresenterTests.swift 17 16 1
PresentersTests/WallpaperPresenterTests.swift 74 71 1 2
ScreenInteractorTests/ScreenInteractorImplTests.swift 2 2
SpectrumInteractorTests/SpectrumInteractorImplTests.swift 20 20
TrackInteractorTests/TrackInteractorAIProcessingTests.swift 3 3
TrackInteractorTests/TrackInteractorArtworkTests.swift 12 12
TrackInteractorTests/TrackInteractorFallbackDisplayTests.swift 1 1
TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift 6 6
ViewsTests/SpectrumViewRenderingTests.swift 1 1
ViewsTests/ViewRenderingTests.swift 8 8
WallpaperInteractorTests/WallpaperInteractorImplTests.swift 2 2
published — 150 箇所: settle にそのまま置換可(`@Published` を await)
箇所 条件
PresentersTests/AppPresenterTests.swift:59 presenter.layout.windowFrame != expected.windowFrame
PresentersTests/AppPresenterTests.swift:93 presenter.layout.windowFrame != initial.windowFrame
PresentersTests/AppPresenterTests.swift:102 presenter.layout.windowFrame != updated.windowFrame
PresentersTests/AppPresenterTests.swift:154 presenter.layout.windowFrame != updated.windowFrame
PresentersTests/AppPresenterTests.swift:201 ripple.screenOrigin != CGPoint(x: 100, y: 40)
PresentersTests/AppPresenterTests.swift:231 ripple.screenOrigin != CGPoint(x: 0, y: 40)
PresentersTests/AppPresenterTests.swift:240 ripple.screenOrigin != CGPoint(x: 1920, y: 40)
PresentersTests/AppPresenterTests.swift:271 presenter.layout.windowFrame != first.windowFrame
PresentersTests/AppPresenterTests.swift:291 counter.count < 3
PresentersTests/AppPresenterTests.swift:312 presenter.layout.windowFrame != initial.windowFrame
PresentersTests/AppPresenterTests.swift:358 presenter.layout.windowFrame == initial.windowFrame
PresentersTests/AppPresenterTests.swift:365 presenter.layout.windowFrame == updated.windowFrame
PresentersTests/AppPresenterTests.swift:391 presenter.layout.windowFrame == initial.windowFrame
PresentersTests/AppPresenterTests.swift:405 presenter.layout.windowFrame == polled.windowFrame
PresentersTests/HeaderPresenterDuplicateTests.swift:69 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterDuplicateTests.swift:107 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterDuplicateTests.swift:115 presenter.artworkImage == nil
PresentersTests/HeaderPresenterDuplicateTests.swift:151 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterDuplicateTests.swift:162 presenter.artworkImage === cachedImage
PresentersTests/HeaderPresenterTests.swift:162 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterTests.swift:187 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterTests.swift:193 presenter.titlePhase != .idle
PresentersTests/HeaderPresenterTests.swift:241 presenter.titleColor == Self.processing
PresentersTests/HeaderPresenterTests.swift:274 presenter.titleColor == Self.processing
PresentersTests/HeaderPresenterTests.swift:277 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterTests.swift:300 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterTests.swift:326 presenter.titlePhase != .revealed || presenter.artistPhase …
PresentersTests/HeaderPresenterTests.swift:377 presenter.titleStyle.fontSize == 32
PresentersTests/HeaderPresenterTests.swift:410 presenter.titleColor == initialProcessing
PresentersTests/HeaderPresenterTests.swift:420 presenter.titleColor == updatedProcessing
PresentersTests/HeaderPresenterTests.swift:462 presenter.displayTitle == "††"
PresentersTests/HeaderPresenterTests.swift:476 presenter.displayTitle == "ΑΑ"
PresentersTests/LyricsPresenterColumnsTests.swift:65 presenter.lyricsState が .success ケースになるまで(switch文でそれ以外は10ms…
PresentersTests/LyricsPresenterColumnsTests.swift:92 presenter.lyricsState が .success ケースになるまで
PresentersTests/LyricsPresenterColumnsTests.swift:119 presenter.lyricsState が .success ケースになるまで
PresentersTests/LyricsPresenterColumnsTests.swift:143 presenter.lyricsState が .success ケースになるまで
PresentersTests/LyricsPresenterDuplicateTests.swift:66 presenter.lyricsState.isSuccess が true になるまで(!isSuccess && …
PresentersTests/LyricsPresenterDuplicateTests.swift:106 presenter.lyricsState.isSuccess が true になるまで
PresentersTests/LyricsPresenterDuplicateTests.swift:157 presenter.lyricsState.isSuccess が true になるまで
PresentersTests/LyricsPresenterTests.swift:154 presenter.lyricStyle.fontSize == 20
PresentersTests/LyricsPresenterTests.swift:180 presenter.lyricsState.isLoading
PresentersTests/LyricsPresenterTests.swift:203 presenter.lyricsState != .failure(ループはこれを満たさなくなるまで継続)
PresentersTests/LyricsPresenterTests.swift:228 presenter.lyricsState.isLoading
PresentersTests/LyricsPresenterTests.swift:235 presenter.lyricsState.isIdle
PresentersTests/LyricsPresenterTests.swift:261 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:293 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:336 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:376 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:428 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:469 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:517 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:554 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:612 presenter.lyricsStateが.success(_)になるまで
PresentersTests/LyricsPresenterTests.swift:649 presenter.lyricsStateが.success(_)になるまで
PresentersTests/SpectrumPresenterTests.swift:148 状態条件なし。直前の config.fire()(147行)で enqueue された appStyleChanges…
PresentersTests/SpectrumPresenterTests.swift:154 状態条件なし。直前の2回目の config.fire()(153行、enabled:falseへの変更後)のmain-…
PresentersTests/SpectrumPresenterTests.swift:184 状態条件なし。直前の config.fire()(183行、スタイルのみ変更後)のmain-queue配信完了を保証する
PresentersTests/SpectrumPresenterTests.swift:190 状態条件なし。2回目の config.fire()(189行、スタイル変更なしの同一pingを再送)のmain-que…
PresentersTests/SpectrumPresenterTests.swift:204 !presenter.binHeights().isEmpty(capturingTickPublishesBars:…
PresentersTests/SpectrumPresenterTests.swift:222 !presenter.binHeights().isEmpty(barCountFollowsWidth: バー本数が…
PresentersTests/SpectrumPresenterTests.swift:238 !presenter.binHeights().isEmpty(stereoCountIsEven: ステレオでのバー…
PresentersTests/SpectrumPresenterTests.swift:253 presenter.isAnimating(zeroWidthNoBars: trackLength未設定でもcapt…
PresentersTests/SpectrumPresenterTests.swift:270 !presenter.binHeights().isEmpty(barsFallAfterCapture 1回目: c…
PresentersTests/SpectrumPresenterTests.swift:274 (presenter.binHeights().first ?? 1) < 1(barsFallAfterCaptur…
PresentersTests/SpectrumPresenterTests.swift:280 presenter.binHeights().isEmpty(barsFallAfterCapture 3回目: 完全…
PresentersTests/SpectrumPresenterTests.swift:298 !presenter.binHeights().isEmpty(attackIsInstant: capturing=…
PresentersTests/SpectrumPresenterTests.swift:370 !presenter.binHeights().isEmpty(framesToClear 1回目: capturin…
PresentersTests/SpectrumPresenterTests.swift:375 (presenter.binHeights().first ?? 1) < 0.5(framesToClear 2回目…
PresentersTests/SpectrumPresenterTests.swift:408 !presenter.binHeights().isEmpty(fallenHeight 1回目: capturing…
PresentersTests/SpectrumPresenterTests.swift:410 (presenter.binHeights().first ?? 1) < 1(fallenHeight 2回目: c…
PresentersTests/WallpaperPresenterTests.swift:184 presenter.wallpaperURL == url
PresentersTests/WallpaperPresenterTests.swift:203 !presenter.isLoading
PresentersTests/WallpaperPresenterTests.swift:226 presenter.wallpaperURL == url
PresentersTests/WallpaperPresenterTests.swift:246 presenter.wallpaperURL == url
PresentersTests/WallpaperPresenterTests.swift:281 counter.count >= 1
PresentersTests/WallpaperPresenterTests.swift:301 !presenter.isLoading
PresentersTests/WallpaperPresenterTests.swift:330 presenter.items.count == 2
PresentersTests/WallpaperPresenterTests.swift:332 recorder.players.count >= 1
PresentersTests/WallpaperPresenterTests.swift:337 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:357 presenter.wallpaperURL == url
PresentersTests/WallpaperPresenterTests.swift:388 recorder.scales.contains(1.2)
PresentersTests/WallpaperPresenterTests.swift:392 recorder.scales.contains(1.6)
PresentersTests/WallpaperPresenterTests.swift:413 presenter.items.count == 3
PresentersTests/WallpaperPresenterTests.swift:418 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:422 presenter.wallpaperURL == c.url
PresentersTests/WallpaperPresenterTests.swift:426 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:450 presenter.items.count == 3
PresentersTests/WallpaperPresenterTests.swift:454 presenter.wallpaperURL == c.url
PresentersTests/WallpaperPresenterTests.swift:457 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:477 presenter.wallpaperURL == item.url
PresentersTests/WallpaperPresenterTests.swift:503 presenter.items.count == 2
PresentersTests/WallpaperPresenterTests.swift:508 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:527 presenter.items.count == 2
PresentersTests/WallpaperPresenterTests.swift:532 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:552 presenter.items.count == 3
PresentersTests/WallpaperPresenterTests.swift:561 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:585 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:591 presenter.items.count == 2
PresentersTests/WallpaperPresenterTests.swift:594 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:615 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:627 presenter.items.count == 2
PresentersTests/WallpaperPresenterTests.swift:632 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:656 presenter.items.count == items.count (=5)
PresentersTests/WallpaperPresenterTests.swift:662 presenter.wallpaperURL != prev
PresentersTests/WallpaperPresenterTests.swift:688 presenter.items.count == 3
PresentersTests/WallpaperPresenterTests.swift:695 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:723 !presenter.isLoading
PresentersTests/WallpaperPresenterTests.swift:755 presenter.showLoadingIndicator
PresentersTests/WallpaperPresenterTests.swift:762 !presenter.showLoadingIndicator
PresentersTests/WallpaperPresenterTests.swift:805 !presenter.isLoading
PresentersTests/WallpaperPresenterTests.swift:831 presenter.player != nil
PresentersTests/WallpaperPresenterTests.swift:857 presenter.player != nil
PresentersTests/WallpaperPresenterTests.swift:884 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:891 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:914 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:940 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:946 presenter.wallpaperURL == nil
PresentersTests/WallpaperPresenterTests.swift:951 presenter.player == nil
PresentersTests/WallpaperPresenterTests.swift:975 presenter.player != nil
PresentersTests/WallpaperPresenterTests.swift:981 counter.count == 1
PresentersTests/WallpaperPresenterTests.swift:1007 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:1008 attachCount.count == 1
PresentersTests/WallpaperPresenterTests.swift:1014 presenter.player == nil
PresentersTests/WallpaperPresenterTests.swift:1020 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:1043 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:1081 presenter.items.count == 2
PresentersTests/WallpaperPresenterTests.swift:1097 presenter.wallpaperURL == a2.url
PresentersTests/WallpaperPresenterTests.swift:1119 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:1125 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:1132 interactor.resolveCount == 3
PresentersTests/WallpaperPresenterTests.swift:1133 !presenter.isLoading
PresentersTests/WallpaperPresenterTests.swift:1140 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:1169 presenter.items.count == 3
PresentersTests/WallpaperPresenterTests.swift:1176 interactor.resolveCount == 2
PresentersTests/WallpaperPresenterTests.swift:1177 !presenter.isLoading
PresentersTests/WallpaperPresenterTests.swift:1180 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:1201 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:1207 !presenter.isLoading
PresentersTests/WallpaperPresenterTests.swift:1219 presenter.wallpaperURL == b.url
PresentersTests/WallpaperPresenterTests.swift:1240 presenter.wallpaperURL == a.url
PresentersTests/WallpaperPresenterTests.swift:1255 !presenter.isLoading
ViewsTests/SpectrumViewRenderingTests.swift:87 !presenter.binHeights().isEmpty (tickUntilBars 内部、35行目で毎tic…
ViewsTests/ViewRenderingTests.swift:127 presenter.displayTitle == "Song"
ViewsTests/ViewRenderingTests.swift:142 presenter.displayTitle == "Song"
ViewsTests/ViewRenderingTests.swift:166 presenter.artworkImage != nil && presenter.displayTitle == …
ViewsTests/ViewRenderingTests.swift:373 presenter.displayLyricLines == ["Line 1", "Line 2", "Line 3…
ViewsTests/ViewRenderingTests.swift:466 wallpaperPresenter.showLoadingIndicator
ViewsTests/ViewRenderingTests.swift:512 configStatusPresenter.invalidConfig != nil
ViewsTests/ViewRenderingTests.swift:586 presenter.invalidConfig != nil
ViewsTests/ViewRenderingTests.swift:613 presenter.invalidConfig != nil
spy-readable — 54 箇所: spy / fake が publish すれば置換可
箇所 条件 書き手
ConfigHotReloadTests/ConfigHotReloadPipelineTests.swift:88 !observed.pinged (かつ ContinuousClock.now < updateDeadline) テスト内ローカル final class Observed(line 65-77)の var pinged: Bool { get { l…
ConfigHotReloadTests/ConfigHotReloadPipelineTests.swift:102 observed.lastInvalid == nil (かつ ContinuousClock.now < inval… 同じ Observed class(line 65-77)の var lastInvalid: ConfigReloadFailure? …
ConfigHotReloadTests/ConfigHotReloadPipelineTests.swift:185 !observed.pinged (かつ ContinuousClock.now < deadline) lenientOptionalSectionReload() 内でローカルに再定義された同名の final class Observed(…
ConfigInteractorTests/ConfigInteractorImplTests.swift:43 !observed.pinged, ContinuousClock.now < deadline observed.pinged はローカル Observed クラス(23-35行目)の NSLock 保護された素の `Bool…
ConfigInteractorTests/ConfigInteractorImplTests.swift:85 observed.invalid == nil, ContinuousClock.now < deadline observed.invalid はローカル Observed クラス(65-77行目)の NSLock 保護された `Confi…
LyricsRepositoryTests/LyricsCandidateWaveTests.swift:163 !waveArrived, ContinuousClock.now < deadline — waveArrived … private var waveArrived = false(154行目)と private var inFlight = 0(…
LyricsRepositoryTests/LyricsCandidateWaveTests.swift:227 !fastAnswered, ContinuousClock.now < deadline — このポーリングは ti… private var fastAnswered = false(205行目)は `private actor SlowHitData…
NowPlayingRepositoryTests/NowPlayingRepositoryTests.swift:283 received.value == nil (nil でなくなるまでポーリング=collector Task が値を配… ReceivedBox.value: `var value: NowPlaying?? { get { state.withLock { …
ScreenInteractorTests/ScreenInteractorImplTests.swift:313 counter.count < 1 (かつ ContinuousClock.now < deadline) ローカルに定義された `final class Counter: @unchecked Sendable { var count = 0 …
ScreenInteractorTests/ScreenInteractorImplTests.swift:340 counter.count < 1 (かつ ContinuousClock.now < deadline) ローカルに定義された `final class Counter: @unchecked Sendable { var count = 0 …
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:18 harness.spectrum.startedPids == [4242] FakeSpectrumUseCase.startedPids: `var startedPids: [Int] { state.with…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:20 harness.capturing.value == true CurrentValueBox.value: `var value: Bool { get { state.withLock { $0 }…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:29 harness.capturing.value == true CurrentValueBox.value(同ファイル258-261行目)。書き込みは Harness.init() の sink(234…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:32 harness.spectrum.stopCount > 0 FakeSpectrumUseCase.stopCount: `var stopCount: Int { state.withLock {…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:34 harness.capturing.value == false CurrentValueBox.value(同ファイル258-261行目)。書き込みは sink(234行目)経由、publish元は S…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:45 harness.spectrum.startedPids == [1, 2] FakeSpectrumUseCase.startedPids(同ファイル272行目)。書き込みは startCapture(pid:)(…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:61 harness.spectrum.stopCount > 0 FakeSpectrumUseCase.stopCount(同ファイル273行目)。書き込みは stopCapture()(293-295…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:70 harness.capturing.value == true CurrentValueBox.value(同ファイル258-261行目)。書き込みは sink(234行目)経由、publish元は S…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:73 harness.capturing.value == false CurrentValueBox.value(同ファイル258-261行目)。書き込みは sink(234行目)経由、publish元は S…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:94 harness.capturing.value == true CurrentValueBox.value(同ファイル258-261行目)。書き込み経路は line 20 と同一。
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:97 harness.spectrum.stopCount > 0 FakeSpectrumUseCase.stopCount(同ファイル273行目)。書き込みは stopCapture()、呼び出し元は …
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:106 harness.capturing.value == true CurrentValueBox.value(同ファイル258-261行目)。書き込み経路は line 20 と同一。
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:116 harness.spectrum.stopCount > 0 FakeSpectrumUseCase.stopCount(同ファイル273行目)。書き込みは stopCapture()、呼び出し元は …
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:135 harness.spectrum.startedPids == [4242, 4242] FakeSpectrumUseCase.startedPids(同ファイル272行目)。書き込みは startCapture(pid:)、…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:137 harness.capturing.value == true CurrentValueBox.value(同ファイル258-261行目)。書き込み経路は line 20 と同一(`subject.se…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:155 harness.spectrum.stopCount > 0 FakeSpectrumUseCase.stopCount(同ファイル273行目)。書き込みは stopCapture()(293-295…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:171 harness.spectrum.stopCount > 0 FakeSpectrumUseCase.stopCount(同ファイル273行目)。書き込みは stopCapture()、呼び出し元は …
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:180 harness.capturing.value == true CurrentValueBox.value(同ファイル258-261行目)。書き込み経路は line 20 と同一。
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:186 harness.spectrum.startedPids.count == 2 FakeSpectrumUseCase.startedPids(同ファイル272行目)。書き込みは startCapture(pid:)、…
SpectrumInteractorTests/SpectrumInteractorImplTests.swift:188 harness.capturing.value == true CurrentValueBox.value(同ファイル258-261行目)。書き込み経路は line 20 と同一(再start後の新Ta…
TrackInteractorTests/TrackInteractorAIProcessingTests.swift:125 collector.contains(where: .aiResolving) UpdateCollector.storage(本ファイル l.58: `private var storage: [TrackUpdat…
TrackInteractorTests/TrackInteractorAIProcessingTests.swift:142 collector.contains { $0.lyricsState == .notFound || $0.lyri… UpdateCollector.storage(本ファイル l.58)— l.138 `let cancellable = interac…
TrackInteractorTests/TrackInteractorAIProcessingTests.swift:157 collector.contains { $0.lyricsState == .notFound || $0.lyri… UpdateCollector.storage(本ファイル l.58)— l.153 `let cancellable = interac…
TrackInteractorTests/TrackInteractorArtworkTests.swift:104 collector.snapshot.count < 1 (artworkEmitsOnDelayedArtwork:… ArtworkCollector.emissions — private var emissions: [Data?] = [] (N…
TrackInteractorTests/TrackInteractorArtworkTests.swift:108 collector.snapshot.count < 2 (artworkEmitsOnDelayedArtwork:… ArtworkCollector.emissions — 上記と同じ (`private var emissions: [Data?] =…
TrackInteractorTests/TrackInteractorArtworkTests.swift:124 collector.snapshot.count < 1 (artworkEmitsOnTitleChange: ar… ArtworkCollector.emissions (private var emissions: [Data?] = [])。in…
TrackInteractorTests/TrackInteractorArtworkTests.swift:127 collector.snapshot.count < 2 (artworkEmitsOnTitleChange: ar… ArtworkCollector.emissions — 同上。
TrackInteractorTests/TrackInteractorArtworkTests.swift:144 collector.snapshot.count < 1 (artworkEmitsOnArtistChange: a… ArtworkCollector.emissions — interactor.artwork.sink (line 137) → Tra…
TrackInteractorTests/TrackInteractorArtworkTests.swift:147 collector.snapshot.count < 2 (artworkEmitsOnArtistChange: a… ArtworkCollector.emissions — 同上。
TrackInteractorTests/TrackInteractorArtworkTests.swift:164 collector.snapshot.count < 1 (artworkSticksThroughSameTrack… ArtworkCollector.emissions — interactor.artwork.sink (line 157) → non…
TrackInteractorTests/TrackInteractorArtworkTests.swift:174 collector.snapshot.count < 2 (artworkSticksThroughSameTrack… ArtworkCollector.emissions — 同上経路。nilイベント自体は removeDuplicates 前段のscan…
TrackInteractorTests/TrackInteractorArtworkTests.swift:189 collector.snapshot.count < 1 (artworkDoesNotStickAcrossNilT… ArtworkCollector.emissions — interactor.artwork.sink (line 183) → non…
TrackInteractorTests/TrackInteractorArtworkTests.swift:195 collector.snapshot.count < 2 (artworkDoesNotStickAcrossNilT… ArtworkCollector.emissions — 同上経路。
TrackInteractorTests/TrackInteractorArtworkTests.swift:210 collector.snapshot.count < 1 (artworkClearsOnTrackChangeWit… ArtworkCollector.emissions — interactor.artwork.sink (line 204) → non…
TrackInteractorTests/TrackInteractorArtworkTests.swift:213 collector.snapshot.count < 2 (artworkClearsOnTrackChangeWit… ArtworkCollector.emissions — 同上経路。
TrackInteractorTests/TrackInteractorFallbackDisplayTests.swift:99 collector.snapshot.count < 3 (fallsBackToRawWhenLyricsNotFo… TrackUpdateCollector.updates — `private var updates: [TrackUpdate] = …
TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift:100 collector.snapshot.count < 1 (emitsSnapshotFields: 1回目のNowP… PositionCollector.emissions — `private var emissions: [PlaybackPositi…
TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift:120 collector.snapshot.count < 1 (emitsNilSnapshotFields: rawEl… PositionCollector.emissions — interactor.playbackPosition.sink (line …
TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift:142 collector.snapshot.count < 1 (emitsMultipleSnapshots: Song … PositionCollector.emissions — interactor.playbackPosition.sink (line …
TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift:148 collector.snapshot.count < 2 (emitsMultipleSnapshots: Song … PositionCollector.emissions — 同上経路。
TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift:171 collector.snapshot.count < 1 (volumeMutePatternSuppressed: … PositionCollector.emissions — interactor.playbackPosition.sink (line …
TrackInteractorTests/TrackInteractorPlaybackPositionTests.swift:186 collector.snapshot.count < 2 (volumeMutePatternSuppressed: … PositionCollector.emissions — interactor.playbackPosition.sink (line …
WallpaperInteractorTests/WallpaperInteractorImplTests.swift:231 collector.events.isEmpty (= collector.events に何か1件でも入るまで待つ) Collector.events(本ファイル223行目): `final class Collector: @unchecked Se…
WallpaperInteractorTests/WallpaperInteractorImplTests.swift:256 collector.events.isEmpty (= collector.events に何か1件でも入るまで待つ) Collector.events(本ファイル248行目、emitsWillSleep とは別インスタンスの同一定義): `final …
external — 43 箇所: 外部要因(subprocess / FS / DispatchSource / tick 駆動)— 期限ポーリングのまま
箇所 条件
ConfigDataSourceTests/ConfigWatchTests.swift:416 onChange.count < 1
ConfigDataSourceTests/ConfigWatchTests.swift:430 onChange.count <= settled
ConfigDataSourceTests/ConfigWatchTests.swift:461 onChange.count < 1
ConfigDataSourceTests/ConfigWatchTests.swift:473 onChange.count == afterRename
ConfigDataSourceTests/ConfigWatchTests.swift:484 onChange.count <= settled
DarwinGatewayTests/DarwinGatewayProcessTests.swift:23 helper(lyra-helperという名のsleepプロセス)が終了していること
DarwinGatewayTests/DarwinGatewayProcessTests.swift:25 gateway.isRunning(pid)がtrueを返すこと
DarwinGatewayTests/DarwinGatewayProcessTests.swift:48 processが終了していること
DarwinGatewayTests/DarwinGatewayProcessTests.swift:50 gateway.isRunning(process.processIdentifier)がtrueを返すこと
DarwinGatewayTests/DarwinGatewayProcessTests.swift:54 SIGTERM送信後、processがreapされていること
DarwinGatewayTests/DarwinGatewayRunProcessTests.swift:72 !FileManager.default.fileExists(atPath: pidFile)
DarwinGatewayTests/DarwinGatewayRunProcessTests.swift:88 !Task.isCancelled
DarwinGatewayTests/DarwinGatewayRunProcessTests.swift:115 !FileManager.default.fileExists(atPath: readyFile)
DarwinGatewayTests/DarwinGatewayRunProcessTests.swift:129 kill(pid, 0) == 0
DarwinGatewayTests/ProcessLockTests.swift:53 holder(Perlのflockホルダープロセス)が終了していること
DarwinGatewayTests/ProcessLockTests.swift:54 lockPath のファイル内容(trim後)が空でないこと(holderがPIDを書き込んだ)
DarwinGatewayTests/ProcessLockTests.swift:66 holderプロセスが終了していること
DarwinGatewayTests/ProcessLockTests.swift:67 lockPathのファイル内容が空でないこと
DarwinGatewayTests/ProcessLockTests.swift:104 lockPathのファイル内容が空でないこと
DarwinGatewayTests/ProcessLockTests.swift:107 holderプロセス(SIGKILL送信済み)がreapされていること
DarwinGatewayTests/ProcessLockTests.swift:119 lockPathのファイル内容が空でないこと
DarwinGatewayTests/ProcessLockTests.swift:122 holderプロセス(SIGKILL送信済み)がreapされていること
DarwinGatewayTests/ProcessLockTests.swift:146 lockPathのファイル内容が空でないこと
DarwinGatewayTests/ProcessLockTests.swift:147 childReadyPath にファイルが存在すること
DarwinGatewayTests/ProcessLockTests.swift:151 holder(親)プロセスがreapされていること
DarwinGatewayTests/ProcessLockTests.swift:205 lockPathのファイル内容が空でないこと
DarwinGatewayTests/ProcessLockTests.swift:206 holderプロセスが終了していること
FileWatchGatewayTests/FileWatchGatewayTests.swift:26 !box.fired, ContinuousClock.now < deadline
FileWatchGatewayTests/FileWatchGatewayTests.swift:52 !box.fired, ContinuousClock.now < deadline
FileWatchGatewayTests/FileWatchGatewayTests.swift:77 !box.fired, ContinuousClock.now < deadline
PresentersTests/AppPresenterTests.swift:364 (ポーリング条件なし。直前のconfig.fire()で.receive(on: DispatchQueue.main…
PresentersTests/AppPresenterTests.swift:397 (ポーリング条件なし。config.fire()によるscreenSelector=.vacantへの切替えとrest…
PresentersTests/LyricsPresenterDuplicateTests.swift:111 presenter.activeLineIndex == 1 になるまで(ループ内で updateActiveLine…
PresentersTests/LyricsPresenterDuplicateTests.swift:124 presenter.activeLineIndex == 2 になるまで
PresentersTests/LyricsPresenterDuplicateTests.swift:161 presenter.activeLineIndex == 1 になるまで(再生中のポジション送信後)
PresentersTests/LyricsPresenterTests.swift:342 presenter.activeLineIndex == 1
PresentersTests/LyricsPresenterTests.swift:387 presenter.activeLineIndex == 1
PresentersTests/LyricsPresenterTests.swift:438 presenter.activeLineIndex == 1
PresentersTests/LyricsPresenterTests.swift:474 presenter.activeLineIndex == 2
PresentersTests/LyricsPresenterTests.swift:484 presenter.activeLineIndex == 0
PresentersTests/LyricsPresenterTests.swift:522 presenter.activeLineIndex == 3
PresentersTests/LyricsPresenterTests.swift:560 presenter.activeLineIndex == 1
PresentersTests/WallpaperPresenterTests.swift:836 presenter.player?.rate != 0 で継続 / 実際に待ちたいのはrate == 0
negative — 13 箇所: 否定アサーション(起きないことの確認)— 別の表現が要る
箇所 条件 理由
ConfigInteractorTests/ConfigInteractorImplTests.swift:181 !observed.pinged が真であり続けること(=キャンセル済み debounceTask が誤って publ… 『何も起きないこと(publish されないこと)』を検証する待機であり、settle は『値が来たら満たされる』形の待機にしか使えないため直接の置き換えができない。加えてこの固定20回の Task.yield は、キ…
PresentersTests/AppPresenterTests.swift:321 (条件式なし。presenter.stop()後にinteractor.changes.send(())してもlayo… settleでは『変化しないこと』を表現できない。100msという固定ディレイはCI負荷次第で不十分/過剰になり得るリスクを抱える。代替案: stop()の副作用(購読解除)を直接観測可能にする、またはポジティブな兄弟…
PresentersTests/HeaderPresenterDuplicateTests.swift:75 (明示的な条件式なし。2回目の subject.send(update) 後に固定時間待ち、その後 titlePhas… 同一 update を2回送っても状態が .revealing に戻らない=再トリガーされないことを確認する negative パターン。settle(_:until:) は「値が来たら成立」を待つ仕組みなので、来ない…
PresentersTests/HeaderPresenterDuplicateTests.swift:125 (明示的な条件式なし。同一 imageData を2回目に送信した後、固定時間待ってから presenter.artw… 同じ artwork データを再送しても artworkImage の参照が変わらない(重複デコードが起きない)ことを確認する negative パターン。settle(_:until:) は値の変化を待つ仕組みであり…
PresentersTests/HeaderPresenterTests.swift:333 (条件なし — presenter.stop() 後に subject.send(...) しても displayTi… 「一定時間待っても状態が変化しないこと」を確認する negative アサーションであり、settle(_:until:)(何かが発行されるのを待つ機構)では表現できない。しかも stop() で購読自体が切れているた…
PresentersTests/LyricsPresenterDuplicateTests.swift:71 条件なし。2回目の同一内容 send 後、状態が変化しない(success のまま revealing に戻らない)こ… 「重複送信では何も起きないこと」を検証する否定的待機。機械的には条件なしのbare Task.sleepであり、fixed-delayの定義例(『pure timing assumption — worst kind』…
PresentersTests/LyricsPresenterDuplicateTests.swift:171 activeLineIndex が 1 から変化しないこと(変化したら早期break、変化しなければ満了まで待ってから… 「一時停止中はactiveLineIndexが変化しないこと」を確認する否定的待機。settle(_:until:)は特定の値へのemitを待つ設計であり『変化が起きないこと』を表現できないため適用不可。本サイトはti…
PresentersTests/LyricsPresenterTests.swift:304 presenter.activeLineIndexがnilのままであること(否定的アサーション。ループはnilでなくな… settleでは表現不可。1秒という短い観測窓の間「activeLineIndexが変化しない」ことを確認する否定アサーションであり、かつ本番のDisplayLinkの代わりにテストが`updateActiveLine…
PresentersTests/LyricsPresenterTests.swift:588 明示的な条件式なし。200ms待った後に一度だけpresenter.lyricsState.isIdleを確認する… 構造的には条件を伴わない固定sleepでありfixed-delayとも取れるが、テストの意図は明確に『状態が変化しないことの確認』であり、settleに置換不能な否定アサーションという性質を優先してnegativeに分…
PresentersTests/LyricsPresenterTests.swift:623 明示的な条件式なし。100ms待った後に一度だけ!enteredRevealingを確認する否定的アサーション(『… 構造的には条件を伴わない固定sleepだが、テストの意図は『.revealingへ再突入しないこと』の確認であり否定アサーションとしてnegativeに分類。settleでは表現できない。
PresentersTests/LyricsPresenterTests.swift:657 明示的な条件式なし。presenter.stop()呼び出し後に新規emissionを送り、200ms待った後に一… 構造的には条件を伴わない固定sleepだが、テスト意図は明確に『stop後は状態が変化しないこと』の確認でありsettleに置換不能な否定アサーション。negativeに分類。
PresentersTests/WallpaperPresenterTests.swift:483 presenter.wallpaperURL != urlBefore 『50ms待って何も起きないこと』を確認する否定待機であり、settleは値が来ないとハングするため直接置換できない。CIが詰まっていると本来起きるべき遷移が間に合わずに誤ってパスする危険もある。別アプローチ(cont…
PresentersTests/WallpaperPresenterTests.swift:620 presenter.wallpaperURL != a.url line483と同型の否定待機。settleでは表現できず、正のイベント(loopCurrent呼び出しなど)をスパイして待つ、または注入クロックで進める代替が必要。
clock — 1 箇所: 本来は clock 注入で解くべき
箇所 条件 理由
PresentersTests/SpectrumPresenterTests.swift:380 !presenter.binHeights().isEmpty かつ ContinuousClock.now < de… このループは『本番コードが注入クロック依存で読む経過時間』を待っているわけではなく、presenter.tick()を同期的に何回呼べば収束するかを数える計測ループで、3秒の壁時計デッドラインは純粋な暴走防止の安全弁に…
fixed-delay — 3 箇所: 条件なしの固定 sleep
箇所 条件 理由
AsyncRunnableCommandTests/AsyncRunnableCommandTests.swift:151 なし。何らかの状態変化を待つポーリングではなく、単に10ms経過するのを待つだけ この待機は『何かの完了を検知する』ための同期プリミティブではなく、SUT である AsyncRunnableCommand.run() のブリッジ実装(DispatchSemaphore + 非構造化 Task、Sou…
LyricsRepositoryTests/LyricsCandidateWaveTests.swift:189 条件なし(無条件のsleep)。成功はテスト対象コード(LyricsRepositoryImpl、production… カタログ上は条件を伴わない『bare Task.sleep』に該当するが、典型的な『祈るような固定スリープ』の悪い形とは異なり、応答が返らない遅い候補を模倣し、上位からのタスクキャンセルで割り込まれることを意図的に期待…
MediaRemoteDataSourceTests/MediaRemoteDataSourceImplTests.swift:480 index == 0, firstYieldDelayNanoseconds > 0 (最初の行の yield 直前に… 条件を持たない固定 Task.sleep なので fixed-delay。1件目の poll() が確実にイテレータを掴んだ状態を作ってから2件目の poll() を並行実行させ、'concurrent polls s…
スイープで拾った scout リスト外のファイル
ファイル パターン 件数 判定
PresentersTests/TestSupport/WaitUntil.swift ContinuousClock, Task.sleep 2 main queue にキューされた @published 更新が反映されるまでデッドライン付きでポーリングする共有ヘルパー本体(呼び出し元あり、孤立ヘルパーではない)。scoutのリストに未掲載だが#347移行対象に含めるべき。
PresentersTests/ConfigStatusPresenterTests.swift waitUntil 3 ConfigInteractor の invalidConfig 発火・解除、および stop() 後の購読解除が反映されるまで待つ(#347と同じ poll-until-deadline パターン)。scoutのリストに未掲載。
CLITests/CompletionCommandTests.swift waitUntilExit (Process) 2 シェル補完スクリプトを生成する子プロセスの終了を同期的に待つだけ。CIタイミング起因のflaky対象ではない別カテゴリの待機。
WallpaperDataSourceTests/YouTubeToolDetectionTests.swift waitUntilExit (Process) 1 外部ツール検出コマンドの子プロセス終了を同期的に待つだけ。#347系の待機問題とは無関係な別カテゴリ。
PresentersTests/FakeConfigInteractor.swift Task.sleep (in doc comment) 1 実コードではなくdocコメント内の文字列一致のみ。実際の待機呼び出しは存在しない誤検知。
TrackInteractorTests/TrackInteractorRaceTests.swift waitFor (CheckedContinuation-based) 7 時間ベースのポーリング/スリープではなく、continuationでイベント到着を待つ設計(settleと同系統の正しいパターン)。誤検知として除外。

対応方針

分類ごとに手段が違う。数の多い順ではなく 依存順 に並べる。

0. settle共有ターゲットへ(前提)

settleTests/AppRouterTests/TestSupport/Settle.swift にあり、SwiftPM のテストターゲットは独立モジュールなので PresentersTests からは呼べない(Package.swiftPresentersTestsPresenters / Domain / Dependencies にしか依存していない)。Tests/TestSupport/ に通常の .target(依存は Combine のみ)を作って settle を移し、使うテストターゲットが依存する。settle を使う suite には AppRouterTests同じ.timeLimit(.minutes(1)) を付ける(値が来ない回帰はハングになるため)。

1. published — 150 箇所: 機械的に置換

await waitUntil { p.x == v }await settle(p.$x) { $0 == v }。複合条件は 1 値 1 settle分割(PR #348 と同じ)。件数順: WallpaperPresenterTests 71、SpectrumPresenterTests 16、LyricsPresenterTests 15、AppPresenterTests 14、HeaderPresenterTests 13、ViewRenderingTests 8、HeaderPresenterDuplicateTests 5、LyricsPresenterColumnsTests 4、LyricsPresenterDuplicateTests 3、SpectrumViewRenderingTests 1。

境界事例: WallpaperPresenterTests:330 が読む WallpaperPresenter.items@Published ではない本番プロパティ。同じ書き込みで更新される @Published の隣接状態を待つか、items@Published にする(小さな本番変更)かを PR で判断。

呼び出し元がなくなった時点で Tests/PresentersTests/TestSupport/WaitUntil.swift と各ファイルの private waitUntil / waitForReveal / waitForLyricsSuccess / waitForItemsLoaded削除

2. spy-readable — 54 箇所: spy を publish させる

条件が読むのはテスト側の collector / stub の plain プロパティで、書き手は nonisolated Task や thread pool。NSLock 保護emissions 配列 + waitForCount が典型: TrackInteractorTestsArtworkCollector / PositionCollector / TrackUpdateCollector(19)、SpectrumInteractorImplTests の stub(20)、ConfigHotReloadPipelineTests 3、TrackInteractorAIProcessingTests 3、ScreenInteractorImplTests 2、WallpaperInteractorImplTests 2、ConfigInteractorImplTests 2、LyricsCandidateWaveTests 2、NowPlayingRepositoryTests 1。

手本はリポジトリ内にある: TrackInteractorRaceTests.TrackUpdateCollector.waitFor(predicate:)CheckedContinuation要素の到着時に再開 する。これを TestSupport の汎用 Collector<Element>waitFor(predicate:) / waitForCount(_:)、期限なし)に一般化し、3 つの同型 collector と stub 群を置き換える。settle同じく suite に .timeLimit

3. external — 43 箇所: 期限ポーリングのまま、ただし理由を名前に

  • subprocess / flock(DarwinGatewayTests 22)、DispatchSource / FSEvents(ConfigWatchTests 5、FileWatchGatewayTests 3): プロセス外の状態に await できる signal はないので poll が正しい。subprocess 側の timeout oracle は test: executeProcess の Oracle 要素を抽象化してフレークを構造的に断つ #340設計ProcessExecutor への clock 注入)に従い、ここでは触らない
  • tick 駆動ループ(LyricsPresenterTests 7、LyricsPresenterDuplicateTests 3、AppPresenterTests 2、WallpaperPresenterTests 1): ループ内でテスト自身が updateActiveLineTick() を呼んで @Published activeLineIndex を再計算させている。書き手はテスト自身なので settle は永久にハングする。SpectrumPresenterTeststickUntil同じ「tick を N 回まで回す」ヘルパー(回数で有界、壁時計不要)に寄せる

残す poll は waitUntil ではなく pollUntil(deadline:) のように 期限が仕様であること が名前から分かる形にし、CLAUDE.md の「読むしかない状態」に該当する旨をコメントに書く。

4. negative — 13 箇所: 「起きないこと」の確認

固定 sleep や短い期限で「状態が変わらない」ことを見ている(LyricsPresenterTests 4、LyricsPresenterDuplicateTests 2、HeaderPresenterDuplicateTests 2、WallpaperPresenterTests 2、AppPresenterTests 1、HeaderPresenterTests 1、ConfigInteractorImplTests 1)。settle は「特定の値への emit」を待つ設計なので表現できない。方針: 番兵イベント区別できる後続イベントを送って settle で待ち、その時点で「途中の遷移がなかった」ことを assert する(LyricsPresenterTests.resolvedDuplicateLyrics$lyricsState.dropFirst().sink で既にやっている形)。番兵を作れない箇所は短い有界 wait を残し、否定アサーションである旨をコメントに書く。

5. 対象外(記録のみ)

箇所 分類 なぜ触らないか
PresentersTests/SpectrumPresenterTests.swift:380 clock tick() を何回呼べば収束するかを数える計測ループ。3 秒の壁時計は暴走防止の安全弁で、clock 注入の対象ではない
LyricsRepositoryTests/LyricsCandidateWaveTests.swift:189 fixed-delay 30 秒 sleep は「応答しない候補」を模す test double で、上位のキャンセルで割り込まれることが期待値
AsyncRunnableCommandTests/AsyncRunnableCommandTests.swift:151 fixed-delay SUT のブリッジ(DispatchSemaphore + 非構造Task)を通すための mock 側 sleep
MediaRemoteDataSourceTests/MediaRemoteDataSourceImplTests.swift:480 fixed-delay 2 つの poll() を確実に並行させるためのタイミング調整。本物の smell だが settle の話ではない — 別 issue 候補

進め方

graph LR
  A["0. TestSupport ターゲット + settle 移設"] --> B["1. published 150 箇所(Presenters / Views)"]
  A --> C["2. Collector 一般化 + spy 54 箇所(Interactor / Repository)"]
  B --> D["3–4. tick ヘルパー / 番兵 / pollUntil 命名(external 43・negative 13)"]
  C --> D
Loading

1 と 2 は独立なので並行可。各 PRmutation 検証settle / Collector.waitFor を no-op 化して移行対象が CI と同じシグネチャで落ちること)と、8 コア飽和の負荷下ストレス(PR #348 と同じ手順)を通す。

完了条件

関連

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions