{"id":47157,"date":"2025-03-21T16:17:58","date_gmt":"2025-03-21T20:17:58","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=47157"},"modified":"2025-07-14T13:46:01","modified_gmt":"2025-07-14T17:46:01","slug":"swift-testing-proposal-exit-tests","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2025\/03\/21\/swift-testing-proposal-exit-tests\/","title":{"rendered":"Swift Testing Proposal: Exit Tests"},"content":{"rendered":"<p><a href=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/testing\/0008-exit-tests.md\">ST-0008<\/a>:<\/p>\n<blockquote cite=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/testing\/0008-exit-tests.md\">\n<p>One of the first enhancement requests we received for Swift Testing was the ability to test for precondition failures and other critical failures that terminate the current process when they occur.<\/p>\n<p>[&#8230;]<\/p>\n<p>This proposal introduces new overloads of the <code>#expect()<\/code> and <code>#require()<\/code>\nmacros that take, as an argument, a closure to be executed in a child process.\nWhen called, these macros spawn a new process using the relevant\nplatform-specific interface (<code>posix_spawn()<\/code>, <code>CreateProcessW()<\/code>, etc.), call\nthe closure from within that process, and suspend the caller until that process\nterminates. The exit status of the process is then compared against a known\nvalue passed to the macro, allowing the test to pass or fail as appropriate.<\/p>\n<\/blockquote>\n\n<p>I had heard talk about changing the behavior of preconditions and assertions during tests so that they would signal back to the testing system instead of actually terminating the process. This is a much more ambitious solution and should be more widely useful. For example, I&rsquo;ve written tests to reproduce crashes that are triggered by a bug, but I can&rsquo;t leave these enabled with XCTest because they&rsquo;ll crash the whole testing system.<\/p>\n\n<blockquote cite=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/testing\/0008-exit-tests.md\">\n<p>Exit tests cannot capture any state originating in the parent process or from the enclosing lexical context.<\/p>\n<\/blockquote>\n\n<p>This is an unsurprising limitation. I&rsquo;m guessing that it won&rsquo;t be much of a problem, but I&rsquo;m not sure without trying it.<\/p>\n\n<blockquote cite=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/main\/proposals\/testing\/0008-exit-tests.md\">\n<p>It is often interesting to examine what is written to the standard output and\nstandard error streams by code running in an exit test. Callers can request that\neither or both stream be captured and included in the result of the call to\n<code>#expect(exitsWith:)<\/code> or <code>#require(exitsWith:)<\/code>.<\/p>\n<p>[&#8230;]<\/p>\n<p>The technical constraints preventing recursive exit test invocation can be resolved if there is a need to do so. However, we don't anticipate that this constraint will be a serious issue for developers.<\/p><\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/12\/17\/issues-adopting-swift-testing\/\">Issues Adopting Swift Testing<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2024\/07\/17\/swift-testing-in-xcode-16\/\">Swift Testing in Xcode 16<\/a><\/li>\n<\/ul>\n\n<p id=\"swift-testing-proposal-exit-tests-update-2025-06-17\">Update (<a href=\"#swift-testing-proposal-exit-tests-update-2025-06-17\">2025-06-17<\/a>): <a href=\"https:\/\/objc.social\/@macguru17\/114694773739941707\">Max Seelemann<\/a>:<\/p>\n<blockquote cite=\"https:\/\/objc.social\/@macguru17\/114694773739941707\">\n<p>I&rsquo;ve been trying out exit tests (thanks for that!) and realized they have a pretty high overhead of at least 70ms (100 in my case).<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/jgrynspan\/exit-test-value-capture\/proposals\/testing\/NNNN-exit-test-value-capturing.md\">Jonathan Grynspan<\/a> (<a href=\"https:\/\/forums.swift.org\/t\/pitch-capturing-values-in-exit-tests\/80494\">forum<\/a>):<\/p>\n<blockquote cite=\"https:\/\/github.com\/swiftlang\/swift-evolution\/blob\/jgrynspan\/exit-test-value-capture\/proposals\/testing\/NNNN-exit-test-value-capturing.md\">\n<p>This proposal extends exit tests to support capturing state from the enclosing context (subject to several practical constraints.)<\/p>\n<\/blockquote>","protected":false},"excerpt":{"rendered":"<p>ST-0008: One of the first enhancement requests we received for Swift Testing was the ability to test for precondition failures and other critical failures that terminate the current process when they occur. [&#8230;] This proposal introduces new overloads of the #expect() and #require() macros that take, as an argument, a closure to be executed in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2025-03-21T20:18:00Z","apple_news_api_id":"e26767aa-d8ad-4c5a-85f0-facfcc606ed5","apple_news_api_modified_at":"2025-06-17T19:18:50Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAA==","apple_news_api_share_url":"https:\/\/apple.news\/A4mdnqtitTFqF8PrPzGBu1Q","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[46,71,901,2796,268],"class_list":["post-47157","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-languagedesign","tag-programming","tag-swift-programming-language","tag-swift-testing","tag-testing"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/47157","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=47157"}],"version-history":[{"count":2,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/47157\/revisions"}],"predecessor-version":[{"id":48114,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/47157\/revisions\/48114"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=47157"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=47157"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=47157"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}