From 1aa5cb5baa84337eed37d8d8f0422f7e097f6756 Mon Sep 17 00:00:00 2001 From: Ryan Fitzgerald Date: Wed, 12 Jul 2017 18:51:59 -0400 Subject: [PATCH] Fix memory leak when using async expectations. Closes #405 --- Sources/Nimble/Utils/Async.swift | 6 +++++- Tests/NimbleTests/AsynchronousTest.swift | 25 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Sources/Nimble/Utils/Async.swift b/Sources/Nimble/Utils/Async.swift index 776b861a1..c684904c3 100644 --- a/Sources/Nimble/Utils/Async.swift +++ b/Sources/Nimble/Utils/Async.swift @@ -105,6 +105,10 @@ internal class AwaitPromise { signal = DispatchSemaphore(value: 1) } + deinit { + signal.signal() + } + /// Resolves the promise with the given result if it has not been resolved. Repeated calls to /// this method will resolve in a no-op. /// @@ -252,7 +256,7 @@ internal class AwaitPromiseBuilder { // Stopping the run loop does not work unless we run only 1 mode _ = RunLoop.current.run(mode: .defaultRunLoopMode, before: .distantFuture) } - self.trigger.timeoutSource.suspend() + self.trigger.timeoutSource.cancel() if let asyncSource = self.trigger.actionSource { asyncSource.cancel() diff --git a/Tests/NimbleTests/AsynchronousTest.swift b/Tests/NimbleTests/AsynchronousTest.swift index 47db724d3..3c417070f 100644 --- a/Tests/NimbleTests/AsynchronousTest.swift +++ b/Tests/NimbleTests/AsynchronousTest.swift @@ -18,6 +18,7 @@ final class AsyncTest: XCTestCase, XCTestCaseProvider { ("testWaitUntilErrorsIfDoneIsCalledMultipleTimes", testWaitUntilErrorsIfDoneIsCalledMultipleTimes), ("testWaitUntilMustBeInMainThread", testWaitUntilMustBeInMainThread), ("testToEventuallyMustBeInMainThread", testToEventuallyMustBeInMainThread), + ("testSubjectUnderTestIsReleasedFromMemory", testSubjectUnderTestIsReleasedFromMemory), ] } @@ -217,4 +218,28 @@ final class AsyncTest: XCTestCase, XCTestCaseProvider { expect(executedAsyncBlock).toEventually(beTruthy()) #endif } + + func testSubjectUnderTestIsReleasedFromMemory() { + final class ClassUnderTest { + var deinitCalled: (() -> Void)? + var count = 0 + deinit { deinitCalled?() } + } + + var subject: ClassUnderTest? = ClassUnderTest() + + if let sub = subject { + expect(sub.count).toEventually(equal(0), timeout: 0.1) + expect(sub.count).toEventuallyNot(equal(1), timeout: 0.1) + } + + waitUntil(timeout: 0.5) { done in + subject?.deinitCalled = { + done() + } + + deferToMainQueue { subject = nil } + } + } + }