Skip to content

Commit fbce6e7

Browse files
author
Harlan Haskins
committed
[StdlibUnittest] Allow tests to require a new process
When testing runtime lookup changes, we want to make sure each test runs in isolation. Add a new modifier, `.requireOwnProcess()`, to enable tests to guarantee they’re run in isolation.
1 parent 76dd00b commit fbce6e7

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

stdlib/private/StdlibUnittest/StdlibUnittest.swift

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ func _childProcess() {
839839
var stderr = _Stderr()
840840
print("\(_stdlibUnittestStreamPrefix);end", to: &stderr)
841841

842-
if !testSuite._testByName(testName).canReuseChildProcessAfterTest {
842+
if testSuite._shouldShutDownChildProcess(forTestNamed: testName) {
843843
return
844844
}
845845
}
@@ -1107,7 +1107,7 @@ class _ParentProcess {
11071107
// Check if the child has sent us "end" markers for the current test.
11081108
if stdoutEnd && stderrEnd {
11091109
var status: ProcessTerminationStatus?
1110-
if !testSuite._testByName(testName).canReuseChildProcessAfterTest {
1110+
if testSuite._shouldShutDownChildProcess(forTestNamed: testName) {
11111111
status = _waitForChild()
11121112
switch status! {
11131113
case .exit(0):
@@ -1147,7 +1147,11 @@ class _ParentProcess {
11471147
return (failed: false, ())
11481148
}
11491149
#endif
1150-
print("\(_stdlibUnittestStreamPrefix);shutdown", to: &_childStdin)
1150+
// If the child process expects an EOF, its stdin fd has already been closed and
1151+
// it will shut itself down automatically.
1152+
if !_childStdin.isClosed {
1153+
print("\(_stdlibUnittestStreamPrefix);shutdown", to: &_childStdin)
1154+
}
11511155

11521156
var childCrashed = false
11531157

@@ -1209,6 +1213,9 @@ class _ParentProcess {
12091213
if t.stdinText != nil {
12101214
print("The test \(fullTestName) requires stdin input and can't be run in-process, marking as failed")
12111215
_anyExpectFailed = true
1216+
} else if t.requiresOwnProcess {
1217+
print("The test \(fullTestName) requires running in a child process and can't be run in-process, marking as failed.")
1218+
_anyExpectFailed = true
12121219
} else {
12131220
_anyExpectFailed = false
12141221
testSuite._runTest(name: t.name, parameter: testParameter)
@@ -1550,6 +1557,17 @@ public final class TestSuite {
15501557
return _tests[_testNameToIndex[testName]!]
15511558
}
15521559

1560+
/// Determines if we should shut down the current test process, i.e. if this
1561+
/// test or the next test requires executing in its own process.
1562+
func _shouldShutDownChildProcess(forTestNamed testName: String) -> Bool {
1563+
let index = _testNameToIndex[testName]!
1564+
if index == _tests.count - 1 { return false }
1565+
let currentTest = _tests[index]
1566+
let nextTest = _tests[index + 1]
1567+
if !currentTest.canReuseChildProcessAfterTest { return true }
1568+
return currentTest.requiresOwnProcess || nextTest.requiresOwnProcess
1569+
}
1570+
15531571
internal enum _TestCode {
15541572
case single(code: () -> Void)
15551573
case parameterized(code: (Int) -> Void, count: Int)
@@ -1564,6 +1582,7 @@ public final class TestSuite {
15641582
let stdinEndsWithEOF: Bool
15651583
let crashOutputMatches: [String]
15661584
let code: _TestCode
1585+
let requiresOwnProcess: Bool
15671586

15681587
/// Whether the test harness should stop reusing the child process after
15691588
/// running this test.
@@ -1601,6 +1620,7 @@ public final class TestSuite {
16011620
var _stdinEndsWithEOF: Bool = false
16021621
var _crashOutputMatches: [String] = []
16031622
var _testLoc: SourceLoc?
1623+
var _requiresOwnProcess: Bool = false
16041624
}
16051625

16061626
init(testSuite: TestSuite, name: String, loc: SourceLoc) {
@@ -1630,6 +1650,11 @@ public final class TestSuite {
16301650
return self
16311651
}
16321652

1653+
public func requireOwnProcess() -> _TestBuilder {
1654+
_data._requiresOwnProcess = true
1655+
return self
1656+
}
1657+
16331658
internal func _build(_ testCode: _TestCode) {
16341659
_testSuite._tests.append(
16351660
_Test(
@@ -1638,7 +1663,8 @@ public final class TestSuite {
16381663
stdinText: _data._stdinText,
16391664
stdinEndsWithEOF: _data._stdinEndsWithEOF,
16401665
crashOutputMatches: _data._crashOutputMatches,
1641-
code: testCode))
1666+
code: testCode,
1667+
requiresOwnProcess: _data._requiresOwnProcess))
16421668
_testSuite._testNameToIndex[_name] = _testSuite._tests.count - 1
16431669
}
16441670

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
6+
import Darwin
7+
#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) || os(Cygwin) || os(Haiku)
8+
import Glibc
9+
#elseif os(Windows)
10+
import MSVCRT
11+
#else
12+
#error("Unsupported platform")
13+
#endif
14+
15+
//
16+
// Test that a test runs in its own child process if asked.
17+
//
18+
19+
enum Globals {
20+
static var modifiedByChildProcess = false
21+
}
22+
23+
var TestSuiteRequireNewProcess = TestSuite("TestSuiteRequireNewProcess")
24+
25+
TestSuiteRequireNewProcess.test("RequireOwnProcessBefore")
26+
.code {
27+
Globals.modifiedByChildProcess = true
28+
}
29+
30+
TestSuiteRequireNewProcess.test("RequireOwnProcess")
31+
.requireOwnProcess()
32+
.code {
33+
expectEqual(false, Globals.modifiedByChildProcess)
34+
Globals.modifiedByChildProcess = true
35+
}
36+
37+
TestSuiteRequireNewProcess.test("ShouldNotReusePreviousProcess") {
38+
expectEqual(false, Globals.modifiedByChildProcess)
39+
}
40+
41+
runAllTests()

0 commit comments

Comments
 (0)