various extensions to improve the property wrapper's versatility and unit tests for the extensions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import ThreadSafe
|
||||
import ThreadSafe
|
||||
|
||||
class ThreadSafetyTest: @unchecked Sendable {
|
||||
|
||||
@@ -46,7 +46,7 @@ class ThreadSafetyTest: @unchecked Sendable {
|
||||
// same as above
|
||||
@ThreadSafe(valueTypeCopyOnWrite: false) var anotherNSString: NSMutableString = "Hello"
|
||||
}
|
||||
|
||||
|
||||
let original = Example()
|
||||
var copy = original
|
||||
|
||||
@@ -66,3 +66,187 @@ class ThreadSafetyTest: @unchecked Sendable {
|
||||
#expect(original.anotherNSString == "Hello, World!")
|
||||
#expect(copy.anotherNSString == "Hello, World!")
|
||||
}
|
||||
|
||||
struct StringConvertibleTest {
|
||||
@Test func testStringInterpolation() {
|
||||
struct TestStruct {
|
||||
@ThreadSafe var text: String = "Hello"
|
||||
}
|
||||
|
||||
let testStruct = TestStruct()
|
||||
|
||||
#expect("\(testStruct)" == #"TestStruct(_text: "Hello")"#)
|
||||
// without the extension "\(wrapped)" will show the type instead of the value
|
||||
#expect("\(testStruct)" != "TestStruct(_text: Swift.String)")
|
||||
}
|
||||
|
||||
@Test func testStringConvertible() {
|
||||
struct TestStruct: CustomStringConvertible, CustomDebugStringConvertible {
|
||||
@ThreadSafe var text: String = "Hello"
|
||||
var description: String { _text.description }
|
||||
var debugDescription: String { _text.debugDescription }
|
||||
}
|
||||
let testStruct = TestStruct()
|
||||
#expect(testStruct.debugDescription == testStruct.text.debugDescription)
|
||||
#expect(testStruct.description == testStruct.text.description)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestEquatable {
|
||||
// without the extension this code will simply fail to compile
|
||||
struct TestStruct<Value: Equatable>: Equatable {
|
||||
@ThreadSafe var value: Value
|
||||
}
|
||||
|
||||
@Test func testIntEquatable() {
|
||||
|
||||
let int1 = TestStruct(value: 1)
|
||||
let int2 = TestStruct(value: 1)
|
||||
let int3 = TestStruct(value: 2)
|
||||
|
||||
#expect(int1 == int2)
|
||||
#expect(int1 != int3)
|
||||
|
||||
let string1 = TestStruct(value: "foo")
|
||||
let string2 = TestStruct(value: "foo")
|
||||
let string3 = TestStruct(value: "bar")
|
||||
|
||||
#expect(string1 == string2)
|
||||
#expect(string1 != string3)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestCustomReflectable {
|
||||
struct Custom: CustomReflectable, Equatable {
|
||||
var a = 1
|
||||
var b = 2
|
||||
var customMirror: Mirror {
|
||||
Mirror(self, children: ["A": a, "B": b])
|
||||
}
|
||||
}
|
||||
|
||||
struct WrappedCustom {
|
||||
@ThreadSafe var value = Custom()
|
||||
}
|
||||
|
||||
@Test func testCustomReflectablePreserved() throws {
|
||||
|
||||
let mirror = Mirror(reflecting: WrappedCustom())
|
||||
|
||||
try #require(mirror.children.count == 1)
|
||||
guard let child = mirror.children.first else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// label of wrapped value will have an underscore prefix
|
||||
#expect(child.label == "_value")
|
||||
|
||||
let childMirror = Mirror(reflecting: child.value)
|
||||
let labels = childMirror.children.compactMap { $0.label }
|
||||
|
||||
#expect(labels == ["A", "B"])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct TestSendable {
|
||||
|
||||
// normally we don't have to do anything if all the members already conform to Sendable.
|
||||
struct SendableStruct: Sendable {
|
||||
var a: Int = 0
|
||||
}
|
||||
|
||||
// when the property wrapper is used without the extension this code won't compile.
|
||||
struct SendableStructWrapped: Sendable {
|
||||
@ThreadSafe var a: Int = 0
|
||||
}
|
||||
|
||||
// @unchecked Sendable puts the responsibility on the dev to ensure thread safety
|
||||
// without @ThreadSafe this class will compile but may cause issues
|
||||
class SenableObj: @unchecked Sendable {
|
||||
@ThreadSafe var a: Int = 0
|
||||
}
|
||||
|
||||
@Test func testSendable() async {
|
||||
let sendable = SendableStruct()
|
||||
await doThingWithSendable(sendable)
|
||||
let sendableWrapped = SendableStructWrapped()
|
||||
await doThingWithSendable(sendableWrapped)
|
||||
}
|
||||
|
||||
func doThingWithSendable(_ senable: Sendable) async {
|
||||
_ = await Task {
|
||||
senable
|
||||
}.value
|
||||
}
|
||||
}
|
||||
|
||||
struct TestCodable {
|
||||
struct TestStruct: Codable, Equatable {
|
||||
struct SubStruct : Codable, Equatable {
|
||||
@ThreadSafe var text: String
|
||||
}
|
||||
@ThreadSafe var text: String
|
||||
@ThreadSafe var number: Int
|
||||
@ThreadSafe var bool: Bool
|
||||
@ThreadSafe var numberArray: [Int]
|
||||
@ThreadSafe var optionalText: String?
|
||||
@ThreadSafe var optionalText2: String?
|
||||
@ThreadSafe var optionalText3: String?
|
||||
@ThreadSafe var sub: SubStruct?
|
||||
}
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
@Test func testDecodeAndEncode() throws {
|
||||
let testJson = """
|
||||
{
|
||||
"text": "Hello",
|
||||
"number": 42,
|
||||
"bool": false,
|
||||
"numberArray": [0, 1, 2],
|
||||
"optionalText": "not null",
|
||||
"optionalText2": null,
|
||||
"sub": {
|
||||
"text": "World"
|
||||
}
|
||||
}
|
||||
"""
|
||||
let decoded = try decoder.decode(TestStruct.self, from: Data(testJson.utf8))
|
||||
|
||||
#expect(decoded.text == "Hello")
|
||||
#expect(decoded.number == 42)
|
||||
#expect(!decoded.bool)
|
||||
#expect(decoded.numberArray == [0, 1, 2])
|
||||
#expect(decoded.optionalText == "not null")
|
||||
#expect(decoded.optionalText2 == nil)
|
||||
#expect(decoded.optionalText3 == nil)
|
||||
#expect(decoded.sub?.text == "World")
|
||||
|
||||
let encoded = try encoder.encode(decoded)
|
||||
let encodedString = String(data: encoded, encoding: .utf8)!
|
||||
|
||||
// nil values won't be encoded into the data
|
||||
#expect(!encodedString.contains("optionalText2"))
|
||||
#expect(!encodedString.contains("optionalText3"))
|
||||
}
|
||||
|
||||
@Test func testEncodeThenDecodeAgain() throws {
|
||||
let aStruct = TestStruct(
|
||||
text: "Hello",
|
||||
number: 42,
|
||||
bool: false,
|
||||
numberArray: [0, 1, 2],
|
||||
optionalText: "not null",
|
||||
optionalText2: nil,
|
||||
optionalText3: "optionalText4",
|
||||
sub: TestStruct.SubStruct(text: "World")
|
||||
)
|
||||
|
||||
let data = try JSONEncoder().encode(aStruct)
|
||||
let decoded = try JSONDecoder().decode(TestStruct.self, from: data)
|
||||
|
||||
#expect(decoded == aStruct)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user