diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1573101 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Test key-path-reflection", + "program": "/Applications/Xcode-13.3.0-Beta.2.app/Contents/Developer/usr/bin/xctest", + "args": [ + ".build/debug/key-path-reflectionPackageTests.xctest" + ], + "cwd": "${workspaceFolder:swift-evolution-staging}", + "preLaunchTask": "swift: Build All" + } + ] +} \ No newline at end of file diff --git a/Package.swift b/Package.swift index 395bfab..7fad588 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.6 //===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project @@ -14,23 +14,23 @@ import PackageDescription let package = Package( - name: "SE0000_KeyPathReflection", + name: "key-path-reflection", products: [ .library( - name: "SE0000_KeyPathReflection", - targets: ["SE0000_KeyPathReflection"]), + name: "KeyPathReflection", + targets: ["KeyPathReflection"]), ], dependencies: [ ], targets: [ - .target(name: "KeyPathReflection_CShims"), + .target(name: "KeyPathReflectionCShims"), .target( - name: "SE0000_KeyPathReflection", - dependencies: ["KeyPathReflection_CShims"] + name: "KeyPathReflection", + dependencies: ["KeyPathReflectionCShims"] ), .testTarget( - name: "SE0000_KeyPathReflectionTests", - dependencies: ["SE0000_KeyPathReflection"] + name: "KeyPathReflectionTests", + dependencies: ["KeyPathReflection"] ), ] ) diff --git a/README.md b/README.md index a64f889..2bf3c2e 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,21 @@ add the following to your `Package.swift` file's dependencies: ) ``` +## Local +#### Build with tests +`swift build --build-tests` + +#### Run Tests with Docker +`swift test --skip-build` + +## Docker + +#### Build Tests with Docker +`docker-compose -f "docker-compose.test.yml" build` + +#### Run Tests with Docker +`docker-compose -f "docker-compose.test.yml" up --abort-on-container-exit --exit-code-from app` + +## Prune +`docker system prune --all --volumes` diff --git a/Sources/SE0000_KeyPathReflection/KeyPathInitialization.swift b/Sources/KeyPathReflection/KeyPathInitialization.swift similarity index 99% rename from Sources/SE0000_KeyPathReflection/KeyPathInitialization.swift rename to Sources/KeyPathReflection/KeyPathInitialization.swift index d64c833..4b15bdf 100644 --- a/Sources/SE0000_KeyPathReflection/KeyPathInitialization.swift +++ b/Sources/KeyPathReflection/KeyPathInitialization.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -import KeyPathReflection_CShims +import KeyPathReflectionCShims // This is a utility within KeyPath.swift in the standard library. If this // gets moved into there, then this goes away, but will have to rethink if this diff --git a/Sources/SE0000_KeyPathReflection/KeyPathIterable.swift b/Sources/KeyPathReflection/KeyPathIterable.swift similarity index 100% rename from Sources/SE0000_KeyPathReflection/KeyPathIterable.swift rename to Sources/KeyPathReflection/KeyPathIterable.swift diff --git a/Sources/SE0000_KeyPathReflection/Metadata.swift b/Sources/KeyPathReflection/Metadata.swift similarity index 99% rename from Sources/SE0000_KeyPathReflection/Metadata.swift rename to Sources/KeyPathReflection/Metadata.swift index 85d3fef..c984f1a 100644 --- a/Sources/SE0000_KeyPathReflection/Metadata.swift +++ b/Sources/KeyPathReflection/Metadata.swift @@ -228,8 +228,10 @@ internal struct ClassMetadata: TypeMetadata, LayoutWrapper { internal struct _ClassMetadata { let _kind: Int let _superclass: Any.Type? +#if !swift(>=5.4) || canImport(ObjectiveC) let _reserved: (Int, Int) let _rodata: Int +#endif let _flags: UInt32 let _instanceAddressPoint: UInt32 let _instanceSize: UInt32 diff --git a/Sources/SE0000_KeyPathReflection/Reflection.swift b/Sources/KeyPathReflection/Reflection.swift similarity index 100% rename from Sources/SE0000_KeyPathReflection/Reflection.swift rename to Sources/KeyPathReflection/Reflection.swift diff --git a/Sources/SE0000_KeyPathReflection/RelativePointers.swift b/Sources/KeyPathReflection/RelativePointers.swift similarity index 98% rename from Sources/SE0000_KeyPathReflection/RelativePointers.swift rename to Sources/KeyPathReflection/RelativePointers.swift index 729571e..e69c16b 100644 --- a/Sources/SE0000_KeyPathReflection/RelativePointers.swift +++ b/Sources/KeyPathReflection/RelativePointers.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// // Source: swift/include/swift/Basic/RelativePointer.h -import KeyPathReflection_CShims +import KeyPathReflectionCShims extension UnsafeRawPointer { /// Returns the underlying raw pointer by stripping the pointer authentication signature. diff --git a/Sources/KeyPathReflection_CShims/Functions.c b/Sources/KeyPathReflectionCShims/Functions.c similarity index 100% rename from Sources/KeyPathReflection_CShims/Functions.c rename to Sources/KeyPathReflectionCShims/Functions.c diff --git a/Sources/KeyPathReflection_CShims/include/Functions.h b/Sources/KeyPathReflectionCShims/include/Functions.h similarity index 100% rename from Sources/KeyPathReflection_CShims/include/Functions.h rename to Sources/KeyPathReflectionCShims/include/Functions.h diff --git a/Tests/KeyPathReflectionTests/CustomKeyPathTests.swift b/Tests/KeyPathReflectionTests/CustomKeyPathTests.swift new file mode 100644 index 0000000..57fcd84 --- /dev/null +++ b/Tests/KeyPathReflectionTests/CustomKeyPathTests.swift @@ -0,0 +1,126 @@ +import XCTest +@testable import KeyPathReflection +import Foundation + +private protocol MyProtocol {} +extension Int: MyProtocol {} + +private final class MyFinalClass { + let float: Float = 0 + var int: Int = 0 + // FIXME: Weak and unowned fields are not supported. + // weak var cls: Class? = nil +} + +private struct MyStruct { + var existential: MyProtocol + var generic: [T] +} + +extension MyStruct: MyProtocol {} +extension MyFinalClass: MyProtocol {} + +/// Asserts that the given named key path collections are equal. +func assertNamedKeyPathsEqual( + _ actual: [(name: String, keyPath: KeyPath)], + _ expected: [(name: String, keyPath: KeyPath)], + file: StaticString = #file, + line: UInt = #line +) { + for (actual, expected) in zip(actual, expected) { + XCTAssertEqual(actual.name, expected.name, file: file, line: line) + XCTAssertEqual(actual.keyPath, expected.keyPath, file: file, line: line) + } +} + +final class CustomKeyPathTests: XCTestCase { + func testStruct() throws { + let s = MyStruct(existential: 1, generic: [2, 3]) + let allKeyPaths = Reflection.allKeyPaths(for: s) + XCTAssertEqual( + allKeyPaths, + [\MyStruct.existential, \MyStruct.generic]) + + let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: s) + assertNamedKeyPathsEqual( + allNamedKeyPaths, + [ + ("existential", \MyStruct.existential), + ("generic", \MyStruct.generic), + ]) + } + + func testClass() throws { + let c = MyFinalClass() + let allKeyPaths = Reflection.allKeyPaths(for: c) + XCTAssertEqual(allKeyPaths, [\MyFinalClass.float, \MyFinalClass.int]) + + let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: c) + assertNamedKeyPathsEqual( + allNamedKeyPaths, + [("float", \MyFinalClass.float), ("int", \MyFinalClass.int)]) + + // FIXME: Handle and test non-final class properties and weak/unowned + // properties. + } + + func testExistential() throws { + let s = MyStruct(existential: 1, generic: [2, 3]) + let c = MyFinalClass() + func test(erasingAs existentialType: T.Type) { + // Struct + let existentialStruct = s as! T + XCTAssertEqual( + Reflection.allKeyPaths(for: existentialStruct), + [\MyStruct.existential, \MyStruct.generic]) + assertNamedKeyPathsEqual( + Reflection.allNamedKeyPaths(for: existentialStruct), + [ + ("existential", \MyStruct.existential), + ("generic", \MyStruct.generic) + ]) + // Class + let existentialClass = c as! T + XCTAssertEqual( + Reflection.allKeyPaths(for: existentialClass), + [\MyFinalClass.float, \MyFinalClass.int]) + assertNamedKeyPathsEqual( + Reflection.allNamedKeyPaths(for: existentialClass), + [("float", \MyFinalClass.float), ("int", \MyFinalClass.int)]) + } + test(erasingAs: Any.self) + test(erasingAs: MyProtocol.self) + } + + func testOptional() throws { + let x: Int? = nil + XCTAssertTrue(Reflection.allKeyPaths(for: x).isEmpty) + var y: Int? = 3 + let yKeyPaths = Reflection.allKeyPaths(for: y) + + XCTAssertEqual(yKeyPaths, [\Optional.!]) + + let concreteYKeyPath = try XCTUnwrap(yKeyPaths[0] as? WritableKeyPath) + + XCTAssertEqual(y[keyPath: concreteYKeyPath], 3) + y[keyPath: concreteYKeyPath] = 4 + XCTAssertEqual(y, 4) + } + + func testArray() throws { + let array = [0, 1, 2, 3] + let allKeyPaths = Reflection.allKeyPaths(for: array) + XCTAssertEqual( + allKeyPaths, + [\[Int][0], \[Int][1], \[Int][2], \[Int][3]]) + let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: array) + assertNamedKeyPathsEqual( + allNamedKeyPaths, + [ + ("0", \[Int][0]), + ("1", \[Int][1]), + ("2", \[Int][2]), + ("3", \[Int][3]), + ]) + } +} diff --git a/Tests/KeyPathReflectionTests/KeyPathReflectionTests.swift b/Tests/KeyPathReflectionTests/KeyPathReflectionTests.swift new file mode 100644 index 0000000..1e00f53 --- /dev/null +++ b/Tests/KeyPathReflectionTests/KeyPathReflectionTests.swift @@ -0,0 +1,95 @@ +import XCTest +@testable import KeyPathReflection + +private protocol MyProtocol {} +extension Int: MyProtocol {} + +private final class MyFinalClass { + let float: Float = 0 + var int: Int = 0 + // FIXME: Weak and unowned fields are not supported. + // weak var cls: Class? = nil +} + +private struct MyStruct { + var existential: MyProtocol + var generic: [T] +} + +extension MyStruct: MyProtocol {} +extension MyFinalClass: MyProtocol {} + + +final class StoredPropertyKeyPathTests: XCTestCase { + func testStruct() throws { + let allKeyPaths = Reflection.allKeyPaths(for: MyStruct.self) + XCTAssertEqual( + allKeyPaths, + [\MyStruct.existential, \MyStruct.generic]) + + let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: MyStruct.self) + assertNamedKeyPathsEqual( + allNamedKeyPaths, + [ + ("existential", \MyStruct.existential), + ("generic", \MyStruct.generic), + ]) + } + + func testClass() throws { + let allKeyPaths = Reflection.allKeyPaths(for: MyFinalClass.self) + XCTAssertEqual(allKeyPaths, [\MyFinalClass.float, \MyFinalClass.int]) + + let allNamedKeyPaths = Reflection.allNamedKeyPaths( + for: MyFinalClass.self) + assertNamedKeyPathsEqual( + allNamedKeyPaths, + [("float", \MyFinalClass.float), ("int", \MyFinalClass.int)]) + + // FIXME: Handle and test non-final class properties and weak/unowned + // properties. + } + + func testExistential() throws { + func test(erasingAs existentialType: T.Type) { + // MyStruct + XCTAssertEqual( + Reflection.allKeyPaths( + forUnderlyingTypeOf: MyStruct.self as! T.Type), + [\MyStruct.existential, \MyStruct.generic]) + assertNamedKeyPathsEqual( + Reflection.allNamedKeyPaths(for: MyStruct.self as! T.Type), + [ + ("existential", \MyStruct.existential), + ("generic", \MyStruct.generic) + ]) + // Class + XCTAssertEqual( + Reflection.allKeyPaths(forUnderlyingTypeOf: MyFinalClass.self as! T.Type), + [\MyFinalClass.float, \MyFinalClass.int]) + assertNamedKeyPathsEqual( + Reflection.allNamedKeyPaths( + forUnderlyingTypeOf: MyFinalClass.self as! T.Type), + [("float", \MyFinalClass.float), ("int", \MyFinalClass.int)]) + } + test(erasingAs: Any.self) + test(erasingAs: MyProtocol.self) + } +} + + +//final class KeyPathReflectionTests: XCTestCase { +// func testStoredPropertyKeyPaths() throws { +// try StoredPropertyKeyPaths.testMyStruct() +//// try StoredPropertyKeyPaths.testClass() +//// try StoredPropertyKeyPaths.testExistential() +// } +// +// func testCustomKeyPaths() throws { +// try CustomKeyPaths.testOptional() +//// try CustomKeyPaths.testMyStruct() +//// try CustomKeyPaths.testExistential() +//// try CustomKeyPaths.testClass() +//// try CustomKeyPaths.testArray() +// } +//} diff --git a/Tests/SE0000_KeyPathReflectionTests/SE0000_KeyPathReflectionTests.swift b/Tests/SE0000_KeyPathReflectionTests/SE0000_KeyPathReflectionTests.swift deleted file mode 100644 index 80e73d6..0000000 --- a/Tests/SE0000_KeyPathReflectionTests/SE0000_KeyPathReflectionTests.swift +++ /dev/null @@ -1,196 +0,0 @@ -import XCTest -@testable import SE0000_KeyPathReflection - -private protocol Protocol {} -extension Int: Protocol {} - -private final class FinalClass { - let float: Float = 0 - var int: Int = 0 - // FIXME: Weak and unowned fields are not supported. - // weak var cls: Class? = nil -} - -private struct Struct { - var existential: Protocol - var generic: [T] -} - -extension Struct: Protocol {} -extension FinalClass: Protocol {} - -/// Asserts that the given named key path collections are equal. -func assertNamedKeyPathsEqual( - _ actual: [(name: String, keyPath: KeyPath)], - _ expected: [(name: String, keyPath: KeyPath)], - file: StaticString = #file, - line: UInt = #line -) { - for (actual, expected) in zip(actual, expected) { - XCTAssertEqual(actual.name, expected.name, file: file, line: line) - XCTAssertEqual(actual.keyPath, expected.keyPath, file: file, line: line) - } -} - -enum StoredPropertyKeyPaths { - static func testStruct() throws { - let allKeyPaths = Reflection.allKeyPaths(for: Struct.self) - XCTAssertEqual( - allKeyPaths, - [\Struct.existential, \Struct.generic]) - - let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: Struct.self) - assertNamedKeyPathsEqual( - allNamedKeyPaths, - [ - ("existential", \Struct.existential), - ("generic", \Struct.generic), - ]) - } - - static func testClass() throws { - let allKeyPaths = Reflection.allKeyPaths(for: FinalClass.self) - XCTAssertEqual(allKeyPaths, [\FinalClass.float, \FinalClass.int]) - - let allNamedKeyPaths = Reflection.allNamedKeyPaths( - for: FinalClass.self) - assertNamedKeyPathsEqual( - allNamedKeyPaths, - [("float", \FinalClass.float), ("int", \FinalClass.int)]) - - // FIXME: Handle and test non-final class properties and weak/unowned - // properties. - } - - static func testExistential() throws { - func test(erasingAs existentialType: T.Type) { - // Struct - XCTAssertEqual( - Reflection.allKeyPaths( - forUnderlyingTypeOf: Struct.self as! T.Type), - [\Struct.existential, \Struct.generic]) - assertNamedKeyPathsEqual( - Reflection.allNamedKeyPaths(for: Struct.self as! T.Type), - [ - ("existential", \Struct.existential), - ("generic", \Struct.generic) - ]) - // Class - XCTAssertEqual( - Reflection.allKeyPaths(forUnderlyingTypeOf: FinalClass.self as! T.Type), - [\FinalClass.float, \FinalClass.int]) - assertNamedKeyPathsEqual( - Reflection.allNamedKeyPaths( - forUnderlyingTypeOf: FinalClass.self as! T.Type), - [("float", \FinalClass.float), ("int", \FinalClass.int)]) - } - test(erasingAs: Any.self) - test(erasingAs: Protocol.self) - } -} - -enum CustomKeyPaths { - static func testStruct() throws { - let s = Struct(existential: 1, generic: [2, 3]) - let allKeyPaths = Reflection.allKeyPaths(for: s) - XCTAssertEqual( - allKeyPaths, - [\Struct.existential, \Struct.generic]) - - let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: s) - assertNamedKeyPathsEqual( - allNamedKeyPaths, - [ - ("existential", \Struct.existential), - ("generic", \Struct.generic), - ]) - } - - static func testClass() throws { - let c = FinalClass() - let allKeyPaths = Reflection.allKeyPaths(for: c) - XCTAssertEqual(allKeyPaths, [\FinalClass.float, \FinalClass.int]) - - let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: c) - assertNamedKeyPathsEqual( - allNamedKeyPaths, - [("float", \FinalClass.float), ("int", \FinalClass.int)]) - - // FIXME: Handle and test non-final class properties and weak/unowned - // properties. - } - - static func testExistential() throws { - let s = Struct(existential: 1, generic: [2, 3]) - let c = FinalClass() - func test(erasingAs existentialType: T.Type) { - // Struct - let existentialStruct = s as! T - XCTAssertEqual( - Reflection.allKeyPaths(for: existentialStruct), - [\Struct.existential, \Struct.generic]) - assertNamedKeyPathsEqual( - Reflection.allNamedKeyPaths(for: existentialStruct), - [ - ("existential", \Struct.existential), - ("generic", \Struct.generic) - ]) - // Class - let existentialClass = c as! T - XCTAssertEqual( - Reflection.allKeyPaths(for: existentialClass), - [\FinalClass.float, \FinalClass.int]) - assertNamedKeyPathsEqual( - Reflection.allNamedKeyPaths(for: existentialClass), - [("float", \FinalClass.float), ("int", \FinalClass.int)]) - } - test(erasingAs: Any.self) - test(erasingAs: Protocol.self) - } - - static func testOptional() throws { - let x: Int? = nil - XCTAssertTrue(Reflection.allKeyPaths(for: x).isEmpty) - var y: Int? = 3 - let yKeyPaths = Reflection.allKeyPaths(for: y) - XCTAssertEqual(yKeyPaths, [\Optional.!]) - let concreteYKeyPath = try XCTUnwrap( - yKeyPaths[0] as? WritableKeyPath) - XCTAssertEqual(y[keyPath: concreteYKeyPath], 3) - y[keyPath: concreteYKeyPath] = 4 - XCTAssertEqual(y, 4) - } - - static func testArray() throws { - let array = [0, 1, 2, 3] - let allKeyPaths = Reflection.allKeyPaths(for: array) - XCTAssertEqual( - allKeyPaths, - [\[Int][0], \[Int][1], \[Int][2], \[Int][3]]) - let allNamedKeyPaths = Reflection.allNamedKeyPaths(for: array) - assertNamedKeyPathsEqual( - allNamedKeyPaths, - [ - ("0", \[Int][0]), - ("1", \[Int][1]), - ("2", \[Int][2]), - ("3", \[Int][3]), - ]) - } -} - -final class SE0000_KeyPathReflectionTests: XCTestCase { - func testStoredPropertyKeyPaths() throws { - try StoredPropertyKeyPaths.testStruct() - try StoredPropertyKeyPaths.testClass() - try StoredPropertyKeyPaths.testExistential() - } - - func testCustomKeyPaths() throws { - try CustomKeyPaths.testOptional() - try CustomKeyPaths.testStruct() - try CustomKeyPaths.testExistential() - try CustomKeyPaths.testClass() - try CustomKeyPaths.testArray() - } -} diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..3cda4c3 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,20 @@ +version: "3.8" # version of docker compose file + +services: + + # app + app: + image: app + build: + context: ./ + dockerfile: test.Dockerfile + depends_on: + - db + networks: + - app-network + command: ["swift", "test", "--skip-build"] + +# networks +networks: + app-network: + driver: bridge diff --git a/test.Dockerfile b/test.Dockerfile new file mode 100644 index 0000000..8044aff --- /dev/null +++ b/test.Dockerfile @@ -0,0 +1,22 @@ +# ================================ +# Build image +# ================================ +# FROM swiftlang/swift:nightly-5.6-focal as build +FROM swiftlang/swift:nightly-main-focal as build + + +# Set up a build area +WORKDIR /build + +# First just resolve dependencies. +# This creates a cached layer that can be reused +# as long as your Package.swift/Package.resolved +# files do not change. +COPY ./Package.* ./ +RUN swift package resolve + +# Copy entire repo into container +COPY . . + +# Build including tests with discovery +RUN swift build --build-tests