diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55d5863ff..fd50a69b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,7 +84,7 @@ swift test by name. - Make sure your pull request includes any necessary updates to the README or other documentation. -- Be sure the unit tests for both the OS X and iOS targets of Nimble +- Be sure the unit tests for both the OS X and iOS targets of Nimble pass before submitting your pull request. You can run all the OS X & iOS unit tests using `./test`. - If you've added a file to the project, make sure it's included in both diff --git a/Nimble.xcodeproj/project.pbxproj b/Nimble.xcodeproj/project.pbxproj index e6e5852f9..332fd30a2 100644 --- a/Nimble.xcodeproj/project.pbxproj +++ b/Nimble.xcodeproj/project.pbxproj @@ -284,6 +284,9 @@ 472FD13B1B9E0CFE00C7B8DA /* HaveCountTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 472FD1361B9E094B00C7B8DA /* HaveCountTest.swift */; }; 4793854D1BA0BB2500296F85 /* ObjCHaveCountTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4793854C1BA0BB2500296F85 /* ObjCHaveCountTest.m */; }; 4793854E1BA0BB2500296F85 /* ObjCHaveCountTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4793854C1BA0BB2500296F85 /* ObjCHaveCountTest.m */; }; + 52F5CD6527EE571C00B19809 /* BackgroundThreadObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5CD6427EE571C00B19809 /* BackgroundThreadObject.swift */; }; + 52F5CD6627EE571C00B19809 /* BackgroundThreadObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5CD6427EE571C00B19809 /* BackgroundThreadObject.swift */; }; + 52F5CD6727EE571C00B19809 /* BackgroundThreadObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5CD6427EE571C00B19809 /* BackgroundThreadObject.swift */; }; 62FB326223B78BF90047BED9 /* BeginWithPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FB326123B78BF90047BED9 /* BeginWithPrefix.swift */; }; 62FB326323B78BF90047BED9 /* BeginWithPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FB326123B78BF90047BED9 /* BeginWithPrefix.swift */; }; 62FB326423B78BF90047BED9 /* BeginWithPrefix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62FB326123B78BF90047BED9 /* BeginWithPrefix.swift */; }; @@ -320,6 +323,7 @@ 857D184F2536124400D8693A /* BeWithinTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857D184D2536123F00D8693A /* BeWithinTest.swift */; }; 857D18502536124400D8693A /* BeWithinTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857D184D2536123F00D8693A /* BeWithinTest.swift */; }; 857D18512536124500D8693A /* BeWithinTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857D184D2536123F00D8693A /* BeWithinTest.swift */; }; + 8913626D29E5C2F500AD535E /* BackgroundThreadObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F5CD6427EE571C00B19809 /* BackgroundThreadObject.swift */; }; 892FDF1329D3EA7700523A80 /* AsyncExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FDF1229D3EA7700523A80 /* AsyncExpression.swift */; }; 892FDF1429D3EA7700523A80 /* AsyncExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FDF1229D3EA7700523A80 /* AsyncExpression.swift */; }; 892FDF1529D3EA7700523A80 /* AsyncExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FDF1229D3EA7700523A80 /* AsyncExpression.swift */; }; @@ -759,6 +763,7 @@ 472FD1341B9E085700C7B8DA /* HaveCount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HaveCount.swift; sourceTree = ""; }; 472FD1361B9E094B00C7B8DA /* HaveCountTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HaveCountTest.swift; sourceTree = ""; }; 4793854C1BA0BB2500296F85 /* ObjCHaveCountTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjCHaveCountTest.m; sourceTree = ""; }; + 52F5CD6427EE571C00B19809 /* BackgroundThreadObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundThreadObject.swift; sourceTree = ""; }; 62FB326123B78BF90047BED9 /* BeginWithPrefix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeginWithPrefix.swift; sourceTree = ""; }; 62FB326523B78D4A0047BED9 /* BeginWithPrefixTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeginWithPrefixTest.swift; sourceTree = ""; }; 6CAEDD091CAEA86F003F1584 /* LinuxSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinuxSupport.swift; sourceTree = ""; }; @@ -890,6 +895,7 @@ 1F14FB63194180C5009F2A08 /* utils.swift */, 1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */, 898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */, + 52F5CD6427EE571C00B19809 /* BackgroundThreadObject.swift */, ); path = Helpers; sourceTree = ""; @@ -1724,6 +1730,7 @@ 7B13BA111DD361EB00C9098C /* ObjCContainElementSatisfyingTest.m in Sources */, 1F925EF6195C147800ED456B /* BeCloseToTest.swift in Sources */, 106112C62251E13B000A5848 /* BeResultTest.swift in Sources */, + 52F5CD6627EE571C00B19809 /* BackgroundThreadObject.swift in Sources */, 1F4A56791A3B32E3009E1637 /* ObjCBeGreaterThanOrEqualToTest.m in Sources */, A8A3B6F6207329DD00E25A08 /* SatisfyAllOfTest.swift in Sources */, AE7ADE491C80C00D00B94CD3 /* MatchErrorTest.swift in Sources */, @@ -1874,6 +1881,7 @@ 89F5E09D290C37B8001F9377 /* OnFailureThrowsTest.swift in Sources */, 1F5DF1A71BDCA10200C3A531 /* EqualTest.swift in Sources */, 106112C72251E13B000A5848 /* BeResultTest.swift in Sources */, + 52F5CD6727EE571C00B19809 /* BackgroundThreadObject.swift in Sources */, CD79C9AA1D2CC848004B6F9A /* ObjCBeLessThanOrEqualToTest.m in Sources */, 1F5DF1931BDCA10200C3A531 /* SynchronousTest.swift in Sources */, CD79C9A11D2CC83B004B6F9A /* ObjCBeCloseToTest.m in Sources */, @@ -2030,6 +2038,7 @@ 7B13BA101DD361EA00C9098C /* ObjCContainElementSatisfyingTest.m in Sources */, 1F925EF7195C147800ED456B /* BeCloseToTest.swift in Sources */, 1F4A567A1A3B32E3009E1637 /* ObjCBeGreaterThanOrEqualToTest.m in Sources */, + 52F5CD6527EE571C00B19809 /* BackgroundThreadObject.swift in Sources */, A8A3B6F5207329DC00E25A08 /* SatisfyAllOfTest.swift in Sources */, AE7ADE4A1C80C00D00B94CD3 /* MatchErrorTest.swift in Sources */, 1F4A568C1A3B3407009E1637 /* ObjCBeTrueTest.m in Sources */, @@ -2135,6 +2144,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8913626D29E5C2F500AD535E /* BackgroundThreadObject.swift in Sources */, D95F8945267EA1E8004B1B4D /* BeNilTest.swift in Sources */, D95F8949267EA1E8004B1B4D /* MatchTest.swift in Sources */, D95F8951267EA1EE004B1B4D /* utils.swift in Sources */, diff --git a/Sources/Nimble/Matchers/PostNotification.swift b/Sources/Nimble/Matchers/PostNotification.swift index 750b46a88..6f604ae8c 100644 --- a/Sources/Nimble/Matchers/PostNotification.swift +++ b/Sources/Nimble/Matchers/PostNotification.swift @@ -5,6 +5,7 @@ import Foundation internal class NotificationCollector { private(set) var observedNotifications: [Notification] + private(set) var observedNotificationDescriptions: [String] private let notificationCenter: NotificationCenter private let names: Set private var tokens: [NSObjectProtocol] @@ -12,6 +13,7 @@ internal class NotificationCollector { required init(notificationCenter: NotificationCenter, names: Set = []) { self.notificationCenter = notificationCenter self.observedNotifications = [] + self.observedNotificationDescriptions = [] self.names = names self.tokens = [] } @@ -21,6 +23,7 @@ internal class NotificationCollector { return notificationCenter.addObserver(forName: name, object: nil, queue: nil) { [weak self] notification in // linux-swift gets confused by .append(n) self?.observedNotifications.append(notification) + self?.observedNotificationDescriptions.append(stringify(notification)) } } @@ -71,7 +74,7 @@ private func _postNotifications( if collector.observedNotifications.isEmpty { actualValue = "no notifications" } else { - actualValue = "<\(stringify(collector.observedNotifications))>" + actualValue = "<\(stringify(collector.observedNotificationDescriptions))>" } var result = try predicate.satisfies(collectorNotificationsExpression) diff --git a/Tests/NimbleTests/Helpers/BackgroundThreadObject.swift b/Tests/NimbleTests/Helpers/BackgroundThreadObject.swift new file mode 100644 index 000000000..0737d63b8 --- /dev/null +++ b/Tests/NimbleTests/Helpers/BackgroundThreadObject.swift @@ -0,0 +1,14 @@ +import Foundation +import Nimble + +// Simulates an object that *really* cares what thread it is run on. +// Supposed to replicate what happens if a NSManagedObject ends up +// in a notification and is picked up by Nimble +class BackgroundThreadObject: CustomDebugStringConvertible { + var debugDescription: String { + if Thread.isMainThread { + fail("This notification was accessed on the main thread when it should have been handled on the thread it was received on") + } + return "BackgroundThreadObject" + } +} diff --git a/Tests/NimbleTests/Matchers/PostNotificationTest.swift b/Tests/NimbleTests/Matchers/PostNotificationTest.swift index c0cc30559..36367aac7 100644 --- a/Tests/NimbleTests/Matchers/PostNotificationTest.swift +++ b/Tests/NimbleTests/Matchers/PostNotificationTest.swift @@ -68,6 +68,46 @@ final class PostNotificationTest: XCTestCase { } }.toEventually(postNotifications(equal([testNotification]), from: notificationCenter)) } + + func testFailsWhenNotificationIsPostedUnexpectedly() { + let n1 = Notification(name: Notification.Name("Foo"), object: nil) + failsWithErrorMessage("expected to not equal <[\(n1)]>, got <[\(n1)]>") { + expect { + self.notificationCenter.post(n1) + }.toNot(postNotifications(equal([n1]), from: self.notificationCenter)) + } + } + + func testPassesWhenNotificationIsNotPosted() { + let n1 = Notification(name: Notification.Name("Foo"), object: nil) + let n2 = Notification(name: Notification.Name(n1.name.rawValue + "a"), object: nil) + expect { + self.notificationCenter.post(n2) + }.toNever(postNotifications(equal([n1]), from: self.notificationCenter)) + } + + func testPassesWhenNotificationIsPostedFromADifferentThread() { + let n1 = Notification(name: Notification.Name("Foo"), object: nil) + expect { + OperationQueue().addOperations([BlockOperation { + let backgroundThreadObject = BackgroundThreadObject() + let n2 = Notification(name: Notification.Name("Bar"), object: backgroundThreadObject) + self.notificationCenter.post(n2) + }], waitUntilFinished: true) + self.notificationCenter.post(n1) + }.to(postNotifications(contain([n1]), from: notificationCenter)) + } + + func testPassesWhenNotificationIsPostedFromADifferentThreadAndToNotCalled() { + let n1 = Notification(name: Notification.Name("Foo"), object: nil) + expect { + OperationQueue().addOperations([BlockOperation { + let backgroundThreadObject = BackgroundThreadObject() + let n2 = Notification(name: Notification.Name(n1.name.rawValue + "a"), object: backgroundThreadObject) + self.notificationCenter.post(n2) + }], waitUntilFinished: true) + }.toNot(postNotifications(equal([n1]), from: notificationCenter)) + } #if os(macOS) func testPassesWhenAllExpectedNotificationsarePostedInDistributedNotificationCenter() {