mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Stress tests are, by definition, stressful. They intentionally burn a lot of resources by using randomness to hopefully surface state machine bugs. Additionally, many stress tests are multi-threaded these days and they may attempt to use all of the available CPUs to better uncover bugs. In isolation, this is not a problem, but the test suite as a whole assumes that individual tests are single threaded and therefore running multiple stress tests at once can quickly spiral out of control. This change formalizes stress tests and then treats them like long tests, i.e. tested via 'check-swift-all' and otherwise opt-in. Finally, with this change, the CI build bots might need to change if they are still only testing 'validation' instead of all of the tests. I see three options: 1) Run all of the tests. -- There are very few long tests left these days, and the additional costs seems small relative to the cost of the whole validation test suite before this change. 2) Continue checking 'validation', now sans stress tests. 3) Check 'validation', *then* the stress tests. If the former doesn't pass, then there is no point in the latter, and by running the stress tests separately, they stand a better chance of uncovering bugs and not overwhelming build bot resources.
118 lines
3.0 KiB
Swift
118 lines
3.0 KiB
Swift
// RUN: %target-run-simple-swift
|
|
// REQUIRES: executable_test
|
|
// REQUIRES: stress_test
|
|
|
|
import StdlibUnittest
|
|
import SwiftPrivatePthreadExtras
|
|
#if os(OSX) || os(iOS)
|
|
import Darwin
|
|
#elseif os(Linux)
|
|
import Glibc
|
|
#endif
|
|
|
|
|
|
var StringTestSuite = TestSuite("String")
|
|
|
|
extension String {
|
|
var capacity: Int {
|
|
return _guts.capacity
|
|
}
|
|
}
|
|
|
|
// Swift.String used to hsve an optimization that allowed us to append to a
|
|
// shared string buffer. However, as lock-free programming invariably does, it
|
|
// introduced a race condition [rdar://25398370 Data Race in StringBuffer.append
|
|
// (found by TSan)].
|
|
//
|
|
// These tests verify that it works correctly when two threads try to append to
|
|
// different non-shared strings that point to the same shared buffer. They used
|
|
// to verify that the first append could succeed without reallocation even if
|
|
// the string was held by another thread, but that has been removed. This could
|
|
// still be an effective thread-safety test, though.
|
|
|
|
enum ThreadID {
|
|
case Primary
|
|
case Secondary
|
|
}
|
|
|
|
var barrierVar: UnsafeMutablePointer<_stdlib_pthread_barrier_t>?
|
|
var sharedString: String = ""
|
|
var secondaryString: String = ""
|
|
|
|
func barrier() {
|
|
var ret = _stdlib_pthread_barrier_wait(barrierVar!)
|
|
expectTrue(ret == 0 || ret == _stdlib_PTHREAD_BARRIER_SERIAL_THREAD)
|
|
}
|
|
|
|
func sliceConcurrentAppendThread(_ tid: ThreadID) {
|
|
for i in 0..<100 {
|
|
barrier()
|
|
if tid == .Primary {
|
|
// Get a fresh buffer.
|
|
sharedString = ""
|
|
sharedString.append("abc")
|
|
sharedString.reserveCapacity(16)
|
|
expectLE(16, sharedString.capacity)
|
|
}
|
|
|
|
barrier()
|
|
|
|
// Get a private string.
|
|
var privateString = sharedString
|
|
|
|
barrier()
|
|
|
|
// Append to the private string.
|
|
if tid == .Primary {
|
|
privateString.append("def")
|
|
} else {
|
|
privateString.append("ghi")
|
|
}
|
|
|
|
barrier()
|
|
|
|
// Verify that contents look good.
|
|
if tid == .Primary {
|
|
expectEqual("abcdef", privateString)
|
|
} else {
|
|
expectEqual("abcghi", privateString)
|
|
}
|
|
expectEqual("abc", sharedString)
|
|
|
|
// Verify that only one thread took ownership of the buffer.
|
|
if tid == .Secondary {
|
|
secondaryString = privateString
|
|
}
|
|
barrier()
|
|
}
|
|
}
|
|
|
|
StringTestSuite.test("SliceConcurrentAppend") {
|
|
barrierVar = UnsafeMutablePointer.allocate(capacity: 1)
|
|
barrierVar!.initialize(to: _stdlib_pthread_barrier_t())
|
|
var ret = _stdlib_pthread_barrier_init(barrierVar!, nil, 2)
|
|
expectEqual(0, ret)
|
|
|
|
let (createRet1, tid1) = _stdlib_pthread_create_block(
|
|
nil, sliceConcurrentAppendThread, .Primary)
|
|
let (createRet2, tid2) = _stdlib_pthread_create_block(
|
|
nil, sliceConcurrentAppendThread, .Secondary)
|
|
|
|
expectEqual(0, createRet1)
|
|
expectEqual(0, createRet2)
|
|
|
|
let (joinRet1, _) = _stdlib_pthread_join(tid1!, Void.self)
|
|
let (joinRet2, _) = _stdlib_pthread_join(tid2!, Void.self)
|
|
|
|
expectEqual(0, joinRet1)
|
|
expectEqual(0, joinRet2)
|
|
|
|
ret = _stdlib_pthread_barrier_destroy(barrierVar!)
|
|
expectEqual(0, ret)
|
|
|
|
barrierVar!.deinitialize(count: 1)
|
|
barrierVar!.deallocate()
|
|
}
|
|
|
|
runAllTests()
|