|  |  |  | // | 
					
						
							|  |  |  | //  Copyright (c) 2018 Open Whisper Systems. All rights reserved. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import Foundation | 
					
						
							|  |  |  | import XCTest | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @testable import SessionServiceKit | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TestJobRecord: SSKJobRecord { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let kJobRecordLabel = "TestJobRecord" | 
					
						
							|  |  |  | class TestJobQueue: JobQueue { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: JobQueue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     typealias DurableOperationType = TestDurableOperation | 
					
						
							|  |  |  |     var jobRecordLabel: String = kJobRecordLabel | 
					
						
							|  |  |  |     static var maxRetries: UInt = 1 | 
					
						
							|  |  |  |     var runningOperations: [TestDurableOperation] = [] | 
					
						
							|  |  |  |     var requiresInternet: Bool = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     func setup() { | 
					
						
							|  |  |  |         defaultSetup() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     func didMarkAsReady(oldJobRecord: TestJobRecord, transaction: YapDatabaseReadWriteTransaction) { | 
					
						
							|  |  |  |         // no special handling | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var isSetup: Bool = false | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let operationQueue = OperationQueue() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     func operationQueue(jobRecord: TestJobRecord) -> OperationQueue { | 
					
						
							|  |  |  |         return self.operationQueue | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     func buildOperation(jobRecord: TestJobRecord, transaction: YapDatabaseReadTransaction) throws -> TestDurableOperation { | 
					
						
							|  |  |  |         return TestDurableOperation(jobRecord: jobRecord, jobBlock: self.jobBlock) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK:  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var jobBlock: (JobRecordType) -> Void = { _ in /* noop */ } | 
					
						
							|  |  |  |     init() { } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TestDurableOperation: OWSOperation, DurableOperation { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: DurableOperation | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var jobRecord: TestJobRecord | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     weak var durableOperationDelegate: TestJobQueue? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var operation: OWSOperation { | 
					
						
							|  |  |  |         return self | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK:  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     var jobBlock: (TestJobRecord) -> Void | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     init(jobRecord: TestJobRecord, jobBlock: @escaping (TestJobRecord) -> Void) { | 
					
						
							|  |  |  |         self.jobRecord = jobRecord | 
					
						
							|  |  |  |         self.jobBlock = jobBlock | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     override func run() { | 
					
						
							|  |  |  |         jobBlock(jobRecord) | 
					
						
							|  |  |  |         self.reportSuccess() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class JobQueueTest: SSKBaseTestSwift { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     override func setUp() { | 
					
						
							|  |  |  |         super.setUp() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     override func tearDown() { | 
					
						
							|  |  |  |         super.tearDown() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK:  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     func buildJobRecord() -> TestJobRecord { | 
					
						
							|  |  |  |         return TestJobRecord(label: kJobRecordLabel) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK:  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     func test_setupMarksInProgressJobsAsReady() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let dispatchGroup = DispatchGroup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let jobQueue = TestJobQueue() | 
					
						
							|  |  |  |         let jobRecord1 = buildJobRecord() | 
					
						
							|  |  |  |         let jobRecord2 = buildJobRecord() | 
					
						
							|  |  |  |         let jobRecord3 = buildJobRecord() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var runList: [TestJobRecord] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         jobQueue.jobBlock = { jobRecord in | 
					
						
							|  |  |  |             runList.append(jobRecord) | 
					
						
							|  |  |  |             dispatchGroup.leave() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.readWrite { transaction in | 
					
						
							|  |  |  |             jobQueue.add(jobRecord: jobRecord1, transaction: transaction) | 
					
						
							|  |  |  |             jobQueue.add(jobRecord: jobRecord2, transaction: transaction) | 
					
						
							|  |  |  |             jobQueue.add(jobRecord: jobRecord3, transaction: transaction) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         dispatchGroup.enter() | 
					
						
							|  |  |  |         dispatchGroup.enter() | 
					
						
							|  |  |  |         dispatchGroup.enter() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let finder = JobRecordFinder() | 
					
						
							|  |  |  |         self.readWrite { transaction in | 
					
						
							|  |  |  |             XCTAssertEqual(3, finder.allRecords(label: kJobRecordLabel, status: .ready, transaction: transaction).count) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // start queue | 
					
						
							|  |  |  |         jobQueue.setup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if case .timedOut = dispatchGroup.wait(timeout: .now() + 1.0) { | 
					
						
							|  |  |  |             XCTFail("timed out waiting for jobs") | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Normally an operation enqueued for a JobRecord by a JobQueue will mark itself as complete | 
					
						
							|  |  |  |         // by deleting itself. | 
					
						
							|  |  |  |         // For testing, the operations enqueued by the TestJobQueue do *not* delete themeselves upon | 
					
						
							|  |  |  |         // completion, simulating an operation which never compeleted. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.readWrite { transaction in | 
					
						
							|  |  |  |             XCTAssertEqual(0, finder.allRecords(label: kJobRecordLabel, status: .ready, transaction: transaction).count) | 
					
						
							|  |  |  |             XCTAssertEqual(3, finder.allRecords(label: kJobRecordLabel, status: .running, transaction: transaction).count) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Verify re-queue | 
					
						
							|  |  |  |         jobQueue.isSetup = false | 
					
						
							|  |  |  |         jobQueue.setup() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.readWrite { transaction in | 
					
						
							|  |  |  |             XCTAssertEqual(3, finder.allRecords(label: kJobRecordLabel, status: .ready, transaction: transaction).count) | 
					
						
							|  |  |  |             XCTAssertEqual(0, finder.allRecords(label: kJobRecordLabel, status: .running, transaction: transaction).count) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let rerunGroup = DispatchGroup() | 
					
						
							|  |  |  |         rerunGroup.enter() | 
					
						
							|  |  |  |         rerunGroup.enter() | 
					
						
							|  |  |  |         rerunGroup.enter() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         var rerunList: [TestJobRecord] = [] | 
					
						
							|  |  |  |         jobQueue.jobBlock = { jobRecord in | 
					
						
							|  |  |  |             rerunList.append(jobRecord) | 
					
						
							|  |  |  |             rerunGroup.leave() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         jobQueue.isSetup = true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         switch rerunGroup.wait(timeout: .now() + 1.0) { | 
					
						
							|  |  |  |         case .timedOut: | 
					
						
							|  |  |  |             XCTFail("timed out waiting for retry") | 
					
						
							|  |  |  |         case .success: | 
					
						
							|  |  |  |             // verify order maintained on requeue | 
					
						
							|  |  |  |             XCTAssertEqual([jobRecord1, jobRecord2, jobRecord3].map { $0.uniqueId }, rerunList.map { $0.uniqueId }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |