| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- //
- // ProtectedTests.swift
- //
- // Copyright (c) 2020 Alamofire Software Foundation (http://alamofire.org/)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- @testable
- import Alamofire
- import XCTest
- final class ProtectedTests: BaseTestCase {
- func testThatProtectedValuesAreAccessedSafely() {
- // Given
- let initialValue = "value"
- let protected = Protected<String>(initialValue)
- // When
- DispatchQueue.concurrentPerform(iterations: 10_000) { i in
- _ = protected.read { $0 }
- protected.write("\(i)")
- }
- // Then
- XCTAssertNotEqual(protected.read { $0 }, initialValue)
- }
- func testThatProtectedAPIIsSafe() {
- // Given
- let initialValue = "value"
- let protected = Protected<String>(initialValue)
- // When
- DispatchQueue.concurrentPerform(iterations: 10_000) { i in
- _ = protected.read { $0 }
- protected.write("\(i)")
- }
- // Then
- XCTAssertNotEqual(protected.read { $0 }, initialValue)
- }
- }
- final class ProtectedWrapperTests: BaseTestCase {
- let value = Protected("value")
- override func setUp() {
- super.setUp()
- value.write("value")
- }
- func testThatWrappedValuesAreAccessedSafely() {
- // Given
- let initialValue = value.read { $0 }
- // When
- DispatchQueue.concurrentPerform(iterations: 10_000) { i in
- _ = value.read { $0 }
- value.write("\(i)")
- }
- // Then
- XCTAssertNotEqual(value.read { $0 }, initialValue)
- }
- }
- final class ProtectedHighContentionTests: BaseTestCase {
- final class StringContainer {
- var totalStrings: Int = 10
- var stringArray = ["this", "is", "a", "simple", "set", "of", "test", "strings", "to", "use"]
- }
- struct StringContainerWriteState {
- var results: [Int] = []
- var completedWrites = 0
- var queue1Complete = false
- var queue2Complete = false
- }
- struct StringContainerReadState {
- var results1: [Int] = []
- var results2: [Int] = []
- var queue1Complete = false
- var queue2Complete = false
- }
- // MARK: - Properties
- let stringContainer = Protected(StringContainer())
- let stringContainerWrite = Protected(StringContainerWriteState())
- let stringContainerRead = Protected(StringContainerReadState())
- func testConcurrentReadWriteBlocks() {
- // Given
- let totalWrites = 4000
- let totalReads = 10_000
- let writeExpectation = expectation(description: "all parallel writes should complete before timeout")
- let readExpectation = expectation(description: "all parallel reads should complete before timeout")
- var writerQueueResults: [Int] = []
- var completedWritesCount = 0
- var readerQueueResults1: [Int] = []
- var readerQueueResults2: [Int] = []
- // When
- executeWriteOperationsInParallel(totalOperationsToExecute: totalWrites) { results, completedOperationCount in
- writerQueueResults = results
- completedWritesCount = completedOperationCount
- writeExpectation.fulfill()
- }
- executeReadOperationsInParallel(totalOperationsToExecute: totalReads) { results1, results2 in
- readerQueueResults1 = results1
- readerQueueResults2 = results2
- readExpectation.fulfill()
- }
- waitForExpectations(timeout: timeout, handler: nil)
- // Then
- XCTAssertEqual(readerQueueResults1.count, totalReads)
- XCTAssertEqual(readerQueueResults2.count, totalReads)
- XCTAssertEqual(writerQueueResults.count, totalWrites)
- XCTAssertEqual(completedWritesCount, totalWrites)
- readerQueueResults1.forEach { XCTAssertEqual($0, 10) }
- readerQueueResults2.forEach { XCTAssertEqual($0, 10) }
- writerQueueResults.forEach { XCTAssertEqual($0, 10) }
- }
- private func executeWriteOperationsInParallel(totalOperationsToExecute totalOperations: Int,
- completion: @escaping ([Int], Int) -> Void) {
- let queue1 = DispatchQueue(label: "com.alamofire.testWriterQueue1")
- let queue2 = DispatchQueue(label: "com.alamofire.testWriterQueue2")
- for _ in 1...totalOperations {
- queue1.async {
- // Moves the last string element to the beginning of the string array
- let result: Int = self.stringContainer.write { stringContainer in
- let lastElement = stringContainer.stringArray.removeLast()
- stringContainer.totalStrings = stringContainer.stringArray.count
- stringContainer.stringArray.insert(lastElement, at: 0)
- stringContainer.totalStrings = stringContainer.stringArray.count
- return stringContainer.totalStrings
- }
- self.stringContainerWrite.write { mutableState in
- mutableState.results.append(result)
- if mutableState.results.count == totalOperations {
- mutableState.queue1Complete = true
- if mutableState.queue2Complete {
- completion(mutableState.results, mutableState.completedWrites)
- }
- }
- }
- }
- queue2.async {
- // Moves the first string element to the end of the string array
- self.stringContainer.write { stringContainer in
- let firstElement = stringContainer.stringArray.remove(at: 0)
- stringContainer.totalStrings = stringContainer.stringArray.count
- stringContainer.stringArray.append(firstElement)
- stringContainer.totalStrings = stringContainer.stringArray.count
- }
- self.stringContainerWrite.write { mutableState in
- mutableState.completedWrites += 1
- if mutableState.completedWrites == totalOperations {
- mutableState.queue2Complete = true
- if mutableState.queue1Complete {
- completion(mutableState.results, mutableState.completedWrites)
- }
- }
- }
- }
- }
- }
- private func executeReadOperationsInParallel(totalOperationsToExecute totalOperations: Int,
- completion: @escaping ([Int], [Int]) -> Void) {
- let queue1 = DispatchQueue(label: "com.alamofire.testReaderQueue1")
- let queue2 = DispatchQueue(label: "com.alamofire.testReaderQueue1")
- for _ in 1...totalOperations {
- queue1.async {
- let result = self.stringContainer.read(\.totalStrings)
- self.stringContainerRead.write {
- $0.results1.append(result)
- if $0.results1.count == totalOperations {
- $0.queue1Complete = true
- if $0.queue2Complete {
- completion($0.results1, $0.results2)
- }
- }
- }
- }
- queue2.async {
- let result = self.stringContainer.read(\.totalStrings)
- self.stringContainerRead.write {
- $0.results2.append(result)
- if $0.results2.count == totalOperations {
- $0.queue2Complete = true
- if $0.queue1Complete {
- completion($0.results1, $0.results2)
- }
- }
- }
- }
- }
- }
- }
|