From 6cf120fdd875c3077a37aa2d0561ec326e8a4e70 Mon Sep 17 00:00:00 2001 From: IKEDA Sho Date: Wed, 27 May 2020 23:17:57 +0900 Subject: [PATCH] Fix focus behavior Let's configure all example groups of QuickSpec subclasses at first using XCTestObservation's `testBundleWillStart(_:)`. --- Quick.xcodeproj/project.pbxproj | 8 +++ Sources/Quick/QuickTestObservation.swift | 69 +++++++++++++++++++ Sources/QuickObjectiveC/QuickSpec.m | 29 ++------ .../QuickFocusedTests/FocusedTests.swift | 7 ++ .../QuickTests/Helpers/QuickSpecRunner.swift | 8 +++ 5 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 Sources/Quick/QuickTestObservation.swift diff --git a/Quick.xcodeproj/project.pbxproj b/Quick.xcodeproj/project.pbxproj index eb6a0f3b9..d6bc16ab8 100644 --- a/Quick.xcodeproj/project.pbxproj +++ b/Quick.xcodeproj/project.pbxproj @@ -140,6 +140,9 @@ CD1F6506226398F600EBE9D8 /* XCTestObservationCenter+QCKSuspendObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1F6502226398F600EBE9D8 /* XCTestObservationCenter+QCKSuspendObservation.swift */; }; CD1F6507226398F600EBE9D8 /* XCTestObservationCenter+QCKSuspendObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1F6502226398F600EBE9D8 /* XCTestObservationCenter+QCKSuspendObservation.swift */; }; CD1F6508226398F600EBE9D8 /* XCTestObservationCenter+QCKSuspendObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1F6502226398F600EBE9D8 /* XCTestObservationCenter+QCKSuspendObservation.swift */; }; + CD21A026247C1385002C4762 /* QuickTestObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21A025247C1385002C4762 /* QuickTestObservation.swift */; }; + CD21A027247C1385002C4762 /* QuickTestObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21A025247C1385002C4762 /* QuickTestObservation.swift */; }; + CD21A028247C1385002C4762 /* QuickTestObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD21A025247C1385002C4762 /* QuickTestObservation.swift */; }; CD264DBD1DDA147A0038B0EB /* AfterSuiteTests+ObjC.m in Sources */ = {isa = PBXBuildFile; fileRef = 64076D241D6D80B500E2B499 /* AfterSuiteTests+ObjC.m */; }; CD582D6F2264B371008F7CE6 /* QuickSpec+MethodList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD582D6E2264B371008F7CE6 /* QuickSpec+MethodList.swift */; }; CD582D702264B371008F7CE6 /* QuickSpec+MethodList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD582D6E2264B371008F7CE6 /* QuickSpec+MethodList.swift */; }; @@ -367,6 +370,7 @@ 8D010A561C11726F00633E2B /* DescribeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DescribeTests.swift; sourceTree = ""; }; AED9C8621CC8A7BD00432F62 /* CrossReferencingSpecs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossReferencingSpecs.swift; sourceTree = ""; }; CD1F6502226398F600EBE9D8 /* XCTestObservationCenter+QCKSuspendObservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestObservationCenter+QCKSuspendObservation.swift"; sourceTree = ""; }; + CD21A025247C1385002C4762 /* QuickTestObservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTestObservation.swift; sourceTree = ""; }; CD261AC81DEC8B0000A8863C /* QuickConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickConfiguration.swift; sourceTree = ""; }; CD3451461E4703D4000C8633 /* QuickMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickMain.swift; sourceTree = ""; }; CD3451471E4703D4000C8633 /* QuickSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickSpec.swift; sourceTree = ""; }; @@ -776,6 +780,7 @@ DA408BDE19FF5599005DF92A /* Hooks */, CD3451461E4703D4000C8633 /* QuickMain.swift */, CD3451471E4703D4000C8633 /* QuickSpec.swift */, + CD21A025247C1385002C4762 /* QuickTestObservation.swift */, 34F375A619515CA700CE1B99 /* World.swift */, 34F3759E19515CA700CE1B99 /* Example.swift */, DA02C91819A8073100093156 /* ExampleMetadata.swift */, @@ -1339,6 +1344,7 @@ CE175D501E8D6B4900EB5E84 /* Behavior.swift in Sources */, CE590E1F1C431FE400253D19 /* QuickTestSuite.swift in Sources */, 1F118D091BDCA536005013A2 /* QuickSpec.m in Sources */, + CD21A028247C1385002C4762 /* QuickTestObservation.swift in Sources */, CDE5BDFF2268CE97006E2F66 /* QuickConfiguration.swift in Sources */, 1F118D011BDCA536005013A2 /* ExampleHooks.swift in Sources */, ); @@ -1426,6 +1432,7 @@ CE175D4F1E8D6B4900EB5E84 /* Behavior.swift in Sources */, CE590E1A1C431FE300253D19 /* QuickTestSuite.swift in Sources */, DA3124E719FCAEE8002858A7 /* DSL.swift in Sources */, + CD21A027247C1385002C4762 /* QuickTestObservation.swift in Sources */, CDE5BDFE2268CE96006E2F66 /* QuickConfiguration.swift in Sources */, DA6B30191A4DB0D500FFB148 /* Filter.swift in Sources */, ); @@ -1561,6 +1568,7 @@ CE175D4E1E8D6B4900EB5E84 /* Behavior.swift in Sources */, 34F375AB19515CA700CE1B99 /* Example.swift in Sources */, DA3124E619FCAEE8002858A7 /* DSL.swift in Sources */, + CD21A026247C1385002C4762 /* QuickTestObservation.swift in Sources */, CDE5BDFD2268CE95006E2F66 /* QuickConfiguration.swift in Sources */, DA6B30181A4DB0D500FFB148 /* Filter.swift in Sources */, ); diff --git a/Sources/Quick/QuickTestObservation.swift b/Sources/Quick/QuickTestObservation.swift new file mode 100644 index 000000000..e508566f4 --- /dev/null +++ b/Sources/Quick/QuickTestObservation.swift @@ -0,0 +1,69 @@ +#if !SWIFT_PACKAGE + +import Foundation +import XCTest + +/// A dummy protocol for calling the internal `+[QuickSpec buildExamplesIfNeeded]` method +/// which is defined in Objective-C from Swift. +@objc internal protocol _QuickSpecInternal { + static func buildExamplesIfNeeded() +} + +@objc internal final class QuickTestObservation: NSObject, XCTestObservation { + @objc(sharedInstance) + static let shared = QuickTestObservation() + + // Quick hooks into this event to compile example groups for each QuickSpec subclasses. + // + // If an exception occurs when compiling examples, report it to the user. Chances are they + // included an expectation outside of a "it", "describe", or "context" block. + func testBundleWillStart(_ testBundle: Bundle) { + QuickSpec.enumerateSubclasses { specClass in + // This relies on `_QuickSpecInternal`. + (specClass as AnyClass).buildExamplesIfNeeded() + } + } +} + +// swiftlint:disable:next todo +// TODO: Unify this with QuickConfiguration's equivalent +extension QuickSpec { + internal static func enumerateSubclasses( + subclasses: [QuickSpec.Type]? = nil, + _ block: (QuickSpec.Type) -> Void + ) { + let subjects: [QuickSpec.Type] + if let subclasses = subclasses { + subjects = subclasses + } else { + let classesCount = objc_getClassList(nil, 0) + + guard classesCount > 0 else { + return + } + + let classes = UnsafeMutablePointer.allocate(capacity: Int(classesCount)) + defer { free(classes) } + + objc_getClassList(AutoreleasingUnsafeMutablePointer(classes), classesCount) + + var specSubclasses: [QuickSpec.Type] = [] + for index in 0.. *selectorNames = [NSMutableSet set]; - - for (Example *example in examples) { - [self addInstanceMethodForExample:example classSelectorNames:selectorNames]; - } - - return [super defaultTestSuite]; -} - /** Invocations for each test method in the test case. QuickSpec overrides this method to define a new method for each example defined in +[QuickSpec spec]. @@ -44,8 +24,6 @@ + (XCTestSuite *)defaultTestSuite { @return An array of invocations that execute the newly defined example methods. */ + (NSArray *)testInvocations { - [self buildExamplesIfNeeded]; - NSArray *examples = [[World sharedWorld] examplesForSpecClass:[self class]]; NSMutableArray *invocations = [NSMutableArray arrayWithCapacity:[examples count]]; @@ -172,3 +150,10 @@ - (void)recordFailureWithDescription:(NSString *)description } @end + +#pragma mark - Test Observation + +__attribute__((constructor)) +static void registerQuickTestObservation(void) { + [[XCTestObservationCenter sharedTestObservationCenter] addTestObserver:[QuickTestObservation sharedInstance]]; +} diff --git a/Tests/QuickTests/QuickFocusedTests/FocusedTests.swift b/Tests/QuickTests/QuickFocusedTests/FocusedTests.swift index 6cb6be751..126167bdd 100644 --- a/Tests/QuickTests/QuickFocusedTests/FocusedTests.swift +++ b/Tests/QuickTests/QuickFocusedTests/FocusedTests.swift @@ -63,10 +63,17 @@ final class FocusedTests: XCTestCase, XCTestCaseProvider { } func testOnlyFocusedExamplesAreExecuted() { + #if SWIFT_PACKAGE let result = qck_runSpecs([ _FunctionalTests_FocusedSpec_Focused.self, _FunctionalTests_FocusedSpec_Unfocused.self, ]) + #else + let result = qck_runSpecs([ + _FunctionalTests_FocusedSpec_Unfocused.self, + _FunctionalTests_FocusedSpec_Focused.self, + ]) + #endif XCTAssertEqual(result?.executionCount, 8) } } diff --git a/Tests/QuickTests/QuickTests/Helpers/QuickSpecRunner.swift b/Tests/QuickTests/QuickTests/Helpers/QuickSpecRunner.swift index d857253ae..bc5792110 100644 --- a/Tests/QuickTests/QuickTests/Helpers/QuickSpecRunner.swift +++ b/Tests/QuickTests/QuickTests/Helpers/QuickSpecRunner.swift @@ -60,6 +60,14 @@ func qck_runSpecs(_ specClasses: [QuickSpec.Type]) -> XCTestRun? { world.isRunningAdditionalSuites = true defer { world.isRunningAdditionalSuites = false } + #if !SWIFT_PACKAGE + // Gather examples first + QuickSpec.enumerateSubclasses(subclasses: specClasses) { specClass in + // This relies on `_QuickSpecInternal`. + (specClass as AnyClass).buildExamplesIfNeeded() + } + #endif + let suite = XCTestSuite(name: "MySpecs") for specClass in specClasses { #if canImport(Darwin)