<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Fastlane on Lessons learned</title><link>https://eghuro.cz/tags/fastlane/</link><description>Recent content in Fastlane on Lessons learned</description><generator>Hugo</generator><language>en-us</language><copyright>© Alexandr Mansurov · Licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)</copyright><lastBuildDate>Mon, 28 Sep 2026 12:16:00 +0200</lastBuildDate><atom:link href="https://eghuro.cz/tags/fastlane/index.xml" rel="self" type="application/rss+xml"/><item><title>xcodebuild says TEST SUCCEEDED, the xcresult says "Issues recorded without an associated test or suite"</title><link>https://eghuro.cz/posts/xcodebuild-succeeds-xcresult-unassociated-failure/</link><pubDate>Mon, 28 Sep 2026 12:16:00 +0200</pubDate><guid>https://eghuro.cz/posts/xcodebuild-succeeds-xcresult-unassociated-failure/</guid><description>&lt;p&gt;A branch passed locally. &lt;code&gt;xcodebuild test&lt;/code&gt; exited 0 and printed:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** TEST SUCCEEDED **&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The fastlane coverage lane on the same branch failed with &lt;code&gt;Tests have failed&lt;/code&gt; and didn&amp;rsquo;t name a test. Scan&amp;rsquo;s xcpretty output doesn&amp;rsquo;t show Swift Testing results, so the log didn&amp;rsquo;t include any failing tests. The result bundle had the answer:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;xcrun xcresulttool get test-results summary --path Test.xcresult&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&amp;#39;result&amp;#39;: &amp;#39;Failed&amp;#39;, &amp;#39;totalTestCount&amp;#39;: 2320, &amp;#39;passedTests&amp;#39;: 2317, &amp;#39;failedTests&amp;#39;: 1, ...}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;testName: Issues recorded without an associated test or suite&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;failureText: Issue recorded: failed - Unimplemented: Unimplemented AppVersionClient.currentVersion …&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; This issue was emitted from tests running in a host application.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; This can lead to false positives, where failures could have emitted from live application code at launch time, and not from the current test.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If your console is green and fastlane, Xcode Cloud or anything else that reads the &lt;code&gt;.xcresult&lt;/code&gt; is red, check the summary for that pseudo-test before you go looking for a flaky test.&lt;/p&gt;&#10;&#10;&lt;h2 id="setup"&gt;Setup &lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Xcode 26, Swift Testing, an app-hosted unit test target (tests run inside the real app process).&lt;/li&gt;&#10;&lt;li&gt;&lt;a&#10; href="https://github.com/pointfreeco/swift-dependencies"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;swift-dependencies&lt;/a&gt;&#10; 1.14, with a client whose &lt;code&gt;testValue&lt;/code&gt; calls &lt;code&gt;IssueReporting.unimplemented(...)&lt;/code&gt;. That&amp;rsquo;s the usual way to make sure a test sets every dependency it uses.&lt;/li&gt;&#10;&lt;li&gt;The app&amp;rsquo;s root view is a login screen. On appear, it asks a backend for the minimum supported version and compares it with &lt;code&gt;currentVersion()&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10;&lt;h2 id="what-happened"&gt;What happened &lt;/h2&gt;&#10;&lt;p&gt;Because the app hosts the tests, it launches as it does for a user, and its login screen sends the version request. When the response arrives, the screen reads the version client inside a test process, which resolves to the unimplemented test value and records an issue. No test is running on that code path, so the issue gets attached to no test.&lt;/p&gt;&#10;&lt;p&gt;The console run only shows it as a warning:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;⚠︎ Test «unknown» recorded a warning at VersionClient.swift:24:1: Issue recorded&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;↳ failed - Unimplemented: Unimplemented AppVersionClient.currentVersion …&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The result bundle counts it as a failure, though, and fastlane reads it.&lt;/p&gt;&#10;&lt;p&gt;Main didn&amp;rsquo;t fail, and the new branch didn&amp;rsquo;t touch any of this code. Its only addition was a new app-hosted suite that took about seven seconds. That extra time let the response arrive while tests were still running. On main, the run was over before the response came back. This is timing-dependent, so a faster backend or a slower one can hide the failure or bring it back.&lt;/p&gt;&#10;&lt;p&gt;Reproduce one target and compare the result bundle summaries:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Run&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Total&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Failed&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Hosted target, as is&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;21&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Same, new suite skipped (&lt;code&gt;-skip-testing&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Same, with the fix&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The 21 in the first row is 20 real tests plus the pseudo-test.&lt;/p&gt;&#10;&#10;&lt;h2 id="dead-end-set-the-dependency-at-launch"&gt;Dead end: set the dependency at launch &lt;/h2&gt;&#10;&lt;p&gt;The obvious fix is to give the app a live value in its entry point:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;prepareDependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;appVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppVersionClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shortVersion&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It built and changed nothing. The same failure showed up again. In a test process, swift-dependencies resets its cached values for each test: it registers an XCTest observer that clears the cache whenever a test case starts. The value you prepare at app launch is gone once the tests start, and the later response reads the test value again.&lt;/p&gt;&#10;&#10;&lt;h2 id="fix-dont-show-the-app-ui-while-hosting-tests"&gt;Fix: don&amp;rsquo;t show the app UI while hosting tests &lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;isTesting&lt;/code&gt; from IssueReporting (re-exported by Dependencies) is true inside a test process. Gate the scene&amp;rsquo;s content on it:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-swift" data-lang="swift"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Dependencies&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;SwiftUI&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;some&lt;/span&gt; &lt;span class="n"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;WindowGroup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Unit tests run inside this app. Its login screen would start a&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// network request whose response reads an unimplemented test dependency.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isTesting&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;RootView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the workaround in the library&amp;rsquo;s &lt;a&#10; href="https://swiftpackageindex.com/pointfreeco/swift-dependencies/main/documentation/dependencies/testing#Testing-gotchas"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;Testing gotchas&lt;/a&gt;&#10; docs. Before applying it, check that no hosted test needs the app&amp;rsquo;s window or a view in the scene. Here, none did, because the tests only needed a real &lt;code&gt;UIApplication&lt;/code&gt;, which still exists with an empty &lt;code&gt;WindowGroup&lt;/code&gt;. The shipping app is unchanged, since &lt;code&gt;isTesting&lt;/code&gt; is false outside a test run.&lt;/p&gt;&#10;&lt;p&gt;With the fix, the hosted target passed 20 of 20, the whole coverage lane reported 0 failures on both schemes, and the test run stopped sending requests to the dev backend.&lt;/p&gt;&#10;&lt;p&gt;That last part is a nice bonus. A test host that shows its real UI talks to real servers from your CI, and anything it logs (request and response bodies, anti-forgery tokens) ends up in your test logs.&lt;/p&gt;&#10;&#10;&lt;h2 id="takeaway"&gt;Takeaway &lt;/h2&gt;&#10;&lt;p&gt;An app-hosted test run also runs your app. Anything the app starts on its own at launch can record a failure that belongs to no test and shows up only in the &lt;code&gt;.xcresult&lt;/code&gt;. Keep the UI out of the test host with &lt;code&gt;if !isTesting&lt;/code&gt;, and when the console and the lane disagree, read &lt;code&gt;xcresulttool get test-results summary&lt;/code&gt; first.&lt;/p&gt;&#10;&lt;p&gt;Try it on your own app-hosted target: grep a recent test log for &lt;code&gt;Test «unknown» recorded&lt;/code&gt;. Each hit is an issue recorded outside any test, and the result bundle may already count it as a failure.&lt;/p&gt;&#10;</description></item></channel></rss>