Add CRDTArray (#12)
This commit is contained in:
parent
bef7ac59f2
commit
8970794cb1
|
@ -16,6 +16,7 @@ opt_in_rules:
|
|||
disabled_rules:
|
||||
- line_length
|
||||
- file_length
|
||||
- opening_brace
|
||||
# Rewrited rules
|
||||
cyclomatic_complexity:
|
||||
warning: 25
|
||||
|
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright 2022 The Yorkie Authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* `CRDTArray` represents Array data type containing logical clocks.
|
||||
*
|
||||
*/
|
||||
class CRDTArray: CRDTContainer {
|
||||
private var elements: RGATreeList
|
||||
|
||||
init(createdAt: TimeTicket, elements: RGATreeList = RGATreeList()) {
|
||||
self.elements = elements
|
||||
super.init(createdAt: createdAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `subPath` returns subPath of JSONPath of the given `createdAt` element.
|
||||
*/
|
||||
override func subPath(createdAt: TimeTicket) throws -> String {
|
||||
return try self.elements.subPath(createdAt: createdAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `purge` physically purge child element.
|
||||
*/
|
||||
override func purge(element: CRDTElement) throws {
|
||||
try self.elements.purge(element)
|
||||
}
|
||||
|
||||
/**
|
||||
* `insert` inserts the given element after the given previous element.
|
||||
*/
|
||||
func insert(value: CRDTElement, afterCreatedAt: TimeTicket) throws {
|
||||
try self.elements.insert(value, afterCreatedAt: afterCreatedAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `move` moves the given `createdAt` element after the `prevCreatedAt`.
|
||||
*/
|
||||
func move(createdAt: TimeTicket, afterCreatedAt: TimeTicket, executedAt: TimeTicket) throws {
|
||||
try self.elements.move(createdAt: createdAt, afterCreatedAt: afterCreatedAt, executedAt: executedAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `get` returns the element of the given createAt.
|
||||
*/
|
||||
func get(createdAt: TimeTicket) throws -> CRDTElement {
|
||||
guard let node = try? self.elements.get(createdAt: createdAt), node.isRemoved() == false else {
|
||||
let log = "can't find the given node: \(createdAt)"
|
||||
Logger.fatal(log)
|
||||
throw YorkieError.unexpected(message: log)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* `get` returns the element of the given index.
|
||||
*/
|
||||
func get(index: Int) throws -> CRDTElement {
|
||||
let node = try self.elements.getNode(index: index)
|
||||
return node.getValue()
|
||||
}
|
||||
|
||||
/**
|
||||
* `getHead` returns dummy head element.
|
||||
*/
|
||||
func getHead() -> CRDTElement {
|
||||
return self.elements.getHead()
|
||||
}
|
||||
|
||||
/**
|
||||
* `getLast` returns last element.
|
||||
*/
|
||||
func getLast() -> CRDTElement {
|
||||
return self.elements.getLast()
|
||||
}
|
||||
|
||||
/**
|
||||
* `getPreviousCreatedAt` returns the creation time of
|
||||
* the previous element of the given element.
|
||||
*/
|
||||
func getPreviousCreatedAt(createdAt: TimeTicket) throws -> TimeTicket {
|
||||
return try self.elements.getPreviousCreatedAt(ofCreatedAt: createdAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `remove` removes the element of the given index.
|
||||
*/
|
||||
@discardableResult
|
||||
func remove(createdAt: TimeTicket, editedAt: TimeTicket) throws -> CRDTElement {
|
||||
return try self.elements.remove(createdAt: createdAt, editedAt: editedAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `remove` removes the element of given index and editedAt.
|
||||
*/
|
||||
func remove(index: Int, editedAt: TimeTicket) throws -> CRDTElement {
|
||||
return try self.elements.remove(index: index, editedAt: editedAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* `getLastCreatedAt` get last created element.
|
||||
*/
|
||||
func getLastCreatedAt() -> TimeTicket {
|
||||
return self.elements.getLastCreatedAt()
|
||||
}
|
||||
|
||||
/**
|
||||
* `length` returns length of this elements.
|
||||
*/
|
||||
var length: Int {
|
||||
return self.elements.length
|
||||
}
|
||||
|
||||
/**
|
||||
* `getDescendants` traverse the descendants of this array.
|
||||
*/
|
||||
override func getDescendants(callback: (_ element: CRDTElement, _ parent: CRDTContainer) -> Bool) {
|
||||
for node in self.elements {
|
||||
let element = node.getValue()
|
||||
if callback(element, self) {
|
||||
return
|
||||
}
|
||||
|
||||
if let element = element as? CRDTContainer {
|
||||
element.getDescendants(callback: callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `toJSON` returns the JSON encoding of this array.
|
||||
*/
|
||||
override func toJSON() -> String {
|
||||
let json = self.elements.map { $0.getValue().toJSON() }
|
||||
|
||||
return "[\(json.joined(separator: ","))]"
|
||||
}
|
||||
|
||||
/**
|
||||
* `toSortedJSON` returns the sorted JSON encoding of this array.
|
||||
*/
|
||||
override func toSortedJSON() -> String {
|
||||
return self.toJSON()
|
||||
}
|
||||
|
||||
/**
|
||||
* `getElements` returns an array of elements contained in this RGATreeList.
|
||||
*/
|
||||
func getElements() -> RGATreeList {
|
||||
return self.elements
|
||||
}
|
||||
|
||||
/**
|
||||
* `deepcopy` copies itself deeply.
|
||||
*/
|
||||
override func deepcopy() -> CRDTArray {
|
||||
let result = CRDTArray(createdAt: self.getCreatedAt())
|
||||
for node in self.elements {
|
||||
try? result.elements.insert(node.getValue().deepcopy(), afterCreatedAt: result.getLastCreatedAt())
|
||||
}
|
||||
result.remove(self.getRemovedAt())
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension CRDTArray: Sequence {
|
||||
typealias Element = CRDTElement
|
||||
|
||||
func makeIterator() -> CRDTArrayIterator {
|
||||
return CRDTArrayIterator(self.elements.makeIterator().next())
|
||||
}
|
||||
}
|
||||
|
||||
class CRDTArrayIterator: IteratorProtocol {
|
||||
private weak var iteratorNext: RGATreeListNode?
|
||||
|
||||
init(_ firstNode: RGATreeListNode?) {
|
||||
self.iteratorNext = firstNode
|
||||
}
|
||||
|
||||
func next() -> CRDTElement? {
|
||||
defer {
|
||||
self.iteratorNext = self.iteratorNext?.getNext()
|
||||
}
|
||||
|
||||
repeat {
|
||||
guard self.iteratorNext != nil else {
|
||||
break
|
||||
}
|
||||
|
||||
if let result = self.iteratorNext, result.isRemoved() == false {
|
||||
return result.getValue()
|
||||
}
|
||||
|
||||
self.iteratorNext = self.iteratorNext?.getNext()
|
||||
|
||||
} while true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -125,13 +125,19 @@ class CRDTElement {
|
|||
}
|
||||
}
|
||||
|
||||
extension CRDTElement: Equatable {
|
||||
static func == (lhs: CRDTElement, rhs: CRDTElement) -> Bool {
|
||||
return lhs.createdAt == rhs.createdAt && lhs.movedAt == rhs.movedAt && lhs.removedAt == rhs.removedAt
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* `CRDTContainer` represents CRDTArray or CRDtObject.
|
||||
* @internal
|
||||
*/
|
||||
class CRDTContainer: CRDTElement {
|
||||
func subPath(createdAt: TimeTicket) -> String? {
|
||||
func subPath(createdAt: TimeTicket) throws -> String {
|
||||
fatalError("Must be implemented.")
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class Primitive: CRDTElement {
|
|||
case .null:
|
||||
return "null"
|
||||
case .boolean(let value):
|
||||
return "\(value)"
|
||||
return "\"\(value)\""
|
||||
case .integer(let value):
|
||||
return "\(value)"
|
||||
case .double(let value):
|
||||
|
|
|
@ -115,7 +115,7 @@ class RGATreeListNode: SplayNode<CRDTElement> {
|
|||
/**
|
||||
* `RGATreeList` is replicated growable array.
|
||||
*/
|
||||
class RGATreeList: Sequence, IteratorProtocol {
|
||||
class RGATreeList {
|
||||
private let dummyHead: RGATreeListNode
|
||||
private var last: RGATreeListNode
|
||||
private var nodeMapByIndex: SplayTree<CRDTElement>
|
||||
|
@ -331,9 +331,9 @@ class RGATreeList: Sequence, IteratorProtocol {
|
|||
}
|
||||
|
||||
/**
|
||||
* `delete` deletes the node of the given creation time.
|
||||
* `remove` removes the node of the given creation time.
|
||||
*/
|
||||
func delete(createdAt: TimeTicket, editedAt: TimeTicket) throws -> CRDTElement {
|
||||
func remove(createdAt: TimeTicket, editedAt: TimeTicket) throws -> CRDTElement {
|
||||
guard let node = self.nodeMapByCreatedAt[createdAt] else {
|
||||
let log = "can't find the given node: \(createdAt)"
|
||||
Logger.fatal(log)
|
||||
|
@ -348,9 +348,9 @@ class RGATreeList: Sequence, IteratorProtocol {
|
|||
}
|
||||
|
||||
/**
|
||||
* `delete` deletes the node of the given index.
|
||||
* `remove` removes the node of the given index.
|
||||
*/
|
||||
func delete(index: Int, editedAt: TimeTicket) throws -> CRDTElement? {
|
||||
func remove(index: Int, editedAt: TimeTicket) throws -> CRDTElement {
|
||||
let node = try self.getNode(index: index)
|
||||
|
||||
if node.remove(editedAt) {
|
||||
|
@ -398,15 +398,21 @@ class RGATreeList: Sequence, IteratorProtocol {
|
|||
|
||||
return result.joined(separator: "-")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Iterator
|
||||
|
||||
extension RGATreeList: Sequence {
|
||||
typealias Element = RGATreeListNode
|
||||
|
||||
func makeIterator() -> RGATreeListIterator {
|
||||
return RGATreeListIterator(self.dummyHead.getNext())
|
||||
}
|
||||
}
|
||||
|
||||
class RGATreeListIterator: IteratorProtocol {
|
||||
private weak var iteratorNext: RGATreeListNode?
|
||||
|
||||
func makeIterator() -> RGATreeList {
|
||||
self.iteratorNext = self.dummyHead.getNext()
|
||||
return self
|
||||
init(_ firstNode: RGATreeListNode?) {
|
||||
self.iteratorNext = firstNode
|
||||
}
|
||||
|
||||
func next() -> RGATreeListNode? {
|
||||
|
|
|
@ -20,7 +20,7 @@ import Foundation
|
|||
* `TimeTicket` is a timestamp of the logical clock. Ticket is immutable.
|
||||
* It is created by `ChangeID`.
|
||||
*/
|
||||
struct TimeTicket {
|
||||
struct TimeTicket: Comparable {
|
||||
private enum InitialValue {
|
||||
static let initialDelimiter: UInt32 = 0
|
||||
static let maxDelemiter: UInt32 = .max
|
||||
|
@ -100,42 +100,19 @@ struct TimeTicket {
|
|||
* `after` returns whether the given ticket was created later.
|
||||
*/
|
||||
func after(_ other: TimeTicket) -> Bool {
|
||||
return self.compare(other) == .orderedDescending
|
||||
return self > other
|
||||
}
|
||||
|
||||
/**
|
||||
* `equals` returns whether the given ticket was created.
|
||||
*/
|
||||
func equals(other: TimeTicket) -> Bool {
|
||||
return self.compare(other) == .orderedSame
|
||||
}
|
||||
|
||||
/**
|
||||
* `compare` returns an integer comparing two Ticket.
|
||||
* The result will be 0 if id==other, -1 if `id < other`, and +1 if `id > other`.
|
||||
* If the receiver or argument is nil, it would panic at runtime.
|
||||
*/
|
||||
func compare(_ other: TimeTicket) -> ComparisonResult {
|
||||
if self.lamport > other.lamport {
|
||||
return .orderedDescending
|
||||
} else if self.lamport < other.lamport {
|
||||
return .orderedAscending
|
||||
static func < (lhs: TimeTicket, rhs: TimeTicket) -> Bool {
|
||||
if lhs.lamport != rhs.lamport {
|
||||
return lhs.lamport < rhs.lamport
|
||||
}
|
||||
|
||||
if let actorID = actorID, let otherActorID = other.actorID {
|
||||
let compare = actorID.localizedCompare(otherActorID)
|
||||
if compare != .orderedSame {
|
||||
return compare
|
||||
}
|
||||
if let lhsActorID = lhs.actorID, let rhsActorID = rhs.actorID, lhsActorID.localizedCompare(rhsActorID) != .orderedSame {
|
||||
return lhsActorID.localizedCompare(rhsActorID) == .orderedAscending
|
||||
}
|
||||
|
||||
if self.delimiter > other.delimiter {
|
||||
return .orderedDescending
|
||||
} else if other.delimiter > self.delimiter {
|
||||
return .orderedAscending
|
||||
}
|
||||
|
||||
return .orderedSame
|
||||
return lhs.delimiter < rhs.delimiter
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -371,14 +371,14 @@ class SplayTree<V> {
|
|||
}
|
||||
|
||||
/**
|
||||
* `deleteRange` separates the range between given 2 boundaries from this Tree.
|
||||
* `removeRange` separates the range between given 2 boundaries from this Tree.
|
||||
* This function separates the range to delete as a subtree
|
||||
* by splaying outer boundary nodes.
|
||||
* leftBoundary must exist because of 0-indexed initial dummy node of tree,
|
||||
* but rightBoundary can be nil means range to delete includes the end of tree.
|
||||
* Refer to the design document in https://github.com/yorkie-team/yorkie/tree/main/design
|
||||
*/
|
||||
func deleteRange(leftBoundary: SplayNode<V>, rightBoundary: SplayNode<V>? = nil) {
|
||||
func removeRange(leftBoundary: SplayNode<V>, rightBoundary: SplayNode<V>? = nil) {
|
||||
guard let rightBoundary = rightBoundary else {
|
||||
self.splayNode(leftBoundary)
|
||||
self.cutOffRight(root: leftBoundary)
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright 2022 The Yorkie Authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import XCTest
|
||||
@testable import Yorkie
|
||||
|
||||
class CRDTArrayTests: XCTestCase {
|
||||
private let actorId = "actor-1"
|
||||
|
||||
func test_get_by_createdAt_returns_a_non_removed_value() throws {
|
||||
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
|
||||
let target = CRDTArray(createdAt: time)
|
||||
|
||||
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
|
||||
|
||||
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
|
||||
|
||||
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
|
||||
|
||||
XCTAssertEqual(target.toJSON(), "[\"11\",\"22\",\"33\"]")
|
||||
|
||||
let resultE2 = try target.get(createdAt: e2.getCreatedAt())
|
||||
XCTAssertNotNil(resultE2)
|
||||
|
||||
let deletedTime = TimeTicket(lamport: 4, delimiter: 0, actorID: actorId)
|
||||
try target.remove(createdAt: e2.getCreatedAt(), editedAt: deletedTime)
|
||||
|
||||
let resultRemovedE2 = try? target.get(createdAt: e2.getCreatedAt())
|
||||
XCTAssertNil(resultRemovedE2)
|
||||
}
|
||||
|
||||
func test_getDescendants_traverse_all_descendants() throws {
|
||||
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
|
||||
let target = CRDTArray(createdAt: time)
|
||||
|
||||
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
|
||||
|
||||
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
|
||||
|
||||
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
|
||||
|
||||
var elemetJsons: [String] = []
|
||||
target.getDescendants { element, _ in
|
||||
elemetJsons.append(element.toJSON())
|
||||
return false
|
||||
}
|
||||
|
||||
XCTAssertEqual(elemetJsons.joined(separator: ", "), "\"11\", \"22\", \"33\"")
|
||||
}
|
||||
|
||||
func test_getDescendants_traverse_one_descendant() throws {
|
||||
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
|
||||
let target = CRDTArray(createdAt: time)
|
||||
|
||||
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
|
||||
|
||||
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
|
||||
|
||||
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
|
||||
|
||||
var elemetJsons: [String] = []
|
||||
target.getDescendants { element, _ in
|
||||
elemetJsons.append(element.toJSON())
|
||||
return true
|
||||
}
|
||||
|
||||
XCTAssertEqual(elemetJsons.joined(separator: ", "), "\"11\"")
|
||||
}
|
||||
|
||||
func test_toJSON() throws {
|
||||
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
|
||||
let target = CRDTArray(createdAt: time)
|
||||
|
||||
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
|
||||
|
||||
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
|
||||
|
||||
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
|
||||
|
||||
XCTAssertEqual(target.toJSON(), "[\"11\",\"22\",\"33\"]")
|
||||
}
|
||||
|
||||
func test_iterator() throws {
|
||||
let time = TimeTicket(lamport: 1, delimiter: 999, actorID: actorId)
|
||||
let target = CRDTArray(createdAt: time)
|
||||
|
||||
let e1 = Primitive(value: .string("11"), createdAt: TimeTicket(lamport: 1, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e1, afterCreatedAt: TimeTicket.initialTimeTicket)
|
||||
|
||||
let e2 = Primitive(value: .string("22"), createdAt: TimeTicket(lamport: 2, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e2, afterCreatedAt: e1.getCreatedAt())
|
||||
|
||||
let e3 = Primitive(value: .string("33"), createdAt: TimeTicket(lamport: 3, delimiter: 0, actorID: actorId))
|
||||
try target.insert(value: e3, afterCreatedAt: e2.getCreatedAt())
|
||||
|
||||
var elemetJsons: [String] = []
|
||||
for each in target {
|
||||
elemetJsons.append(each.toJSON())
|
||||
}
|
||||
|
||||
XCTAssertEqual(elemetJsons.joined(separator: ", "), "\"11\", \"22\", \"33\"")
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ class CRDTElementTests: XCTestCase {
|
|||
let movedResult = target.setMovedAt(small)
|
||||
|
||||
XCTAssertEqual(movedResult, true)
|
||||
XCTAssertEqual(target.getMovedAt()?.compare(small), .orderedSame)
|
||||
XCTAssertTrue(target.getMovedAt() == small)
|
||||
}
|
||||
|
||||
func test_can_set_bigger_movedAt_when_movedAt_is_nil() {
|
||||
|
@ -37,7 +37,7 @@ class CRDTElementTests: XCTestCase {
|
|||
let movedResult = target.setMovedAt(big)
|
||||
|
||||
XCTAssertEqual(movedResult, true)
|
||||
XCTAssertEqual(target.getMovedAt()?.compare(big), .orderedSame)
|
||||
XCTAssertTrue(target.getMovedAt() == big)
|
||||
}
|
||||
|
||||
func test_can_not_set_bigger_movedAt_when_movedAt_is_non_nil() {
|
||||
|
@ -51,7 +51,8 @@ class CRDTElementTests: XCTestCase {
|
|||
let movedResult = target.setMovedAt(timeTicket)
|
||||
|
||||
XCTAssertEqual(movedResult, false)
|
||||
XCTAssertEqual(target.getMovedAt()?.compare(small), .orderedDescending)
|
||||
|
||||
XCTAssertTrue(target.getMovedAt()! > small)
|
||||
}
|
||||
|
||||
func test_can_not_remove_when_nil() {
|
||||
|
|
|
@ -265,7 +265,7 @@ class RGATreeListTests: XCTestCase {
|
|||
XCTAssertEqual(target.getStructureAsString(),
|
||||
"[1:999:0:\"A1\"]-[2:999:0:\"B12\"]-[3:999:0:\"C123\"]")
|
||||
|
||||
let result = try target.delete(createdAt: e2.getCreatedAt(), editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
|
||||
let result = try target.remove(createdAt: e2.getCreatedAt(), editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
|
||||
|
||||
XCTAssertEqual(result.isRemoved(), true)
|
||||
XCTAssertEqual(target.getStructureAsString(),
|
||||
|
@ -286,9 +286,9 @@ class RGATreeListTests: XCTestCase {
|
|||
XCTAssertEqual(target.getStructureAsString(),
|
||||
"[1:999:0:\"A1\"]-[2:999:0:\"B12\"]-[3:999:0:\"C123\"]")
|
||||
|
||||
let result = try target.delete(index: 1, editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
|
||||
let result = try target.remove(index: 1, editedAt: TimeTicket(lamport: 4, delimiter: 0, actorID: self.actorId))
|
||||
|
||||
XCTAssertEqual(result?.isRemoved(), true)
|
||||
XCTAssertEqual(result.isRemoved(), true)
|
||||
XCTAssertEqual(target.getStructureAsString(),
|
||||
"[1:999:0:\"A1\"]-{2:999:0:\"B12\"}-[3:999:0:\"C123\"]")
|
||||
}
|
||||
|
|
|
@ -22,20 +22,20 @@ class TimeTicketTests: XCTestCase {
|
|||
let small = TimeTicket.initialTimeTicket
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
|
||||
XCTAssertEqual(small.compare(big), .orderedAscending)
|
||||
XCTAssertTrue(small < big)
|
||||
}
|
||||
|
||||
func test_compare_with_a_small_thing() {
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
let small = TimeTicket.initialTimeTicket
|
||||
|
||||
XCTAssertEqual(big.compare(small), .orderedDescending)
|
||||
XCTAssertTrue(big > small)
|
||||
}
|
||||
|
||||
func test_compare_with_a_same_thing() {
|
||||
let big = TimeTicket.maxTimeTicket
|
||||
let big2 = TimeTicket.maxTimeTicket
|
||||
|
||||
XCTAssertEqual(big.compare(big2), .orderedSame)
|
||||
XCTAssertTrue(big == big2)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ class SplayTreeTests: XCTestCase {
|
|||
XCTAssertEqual("[1,1]A[3,2]BB[6,3]CCC[10,4]DDDD[15,5]EEEEE[19,4]FFFF[22,3]GGG[24,2]HH[25,1]I", testTree.tree.getStructureAsString())
|
||||
self.removeNodes(testTree.nodes, from: 7, to: 8)
|
||||
XCTAssertEqual("[1,1]A[3,2]BB[6,3]CCC[10,4]DDDD[15,5]EEEEE[19,4]FFFF[22,3]GGG[24,0]HH[25,0]I", testTree.tree.getStructureAsString())
|
||||
testTree.tree.deleteRange(leftBoundary: testTree.nodes[6])
|
||||
testTree.tree.removeRange(leftBoundary: testTree.nodes[6])
|
||||
XCTAssertEqual(testTree.tree.indexOf(testTree.nodes[6]), 19)
|
||||
XCTAssertEqual("[1,1]A[3,2]BB[6,3]CCC[10,4]DDDD[15,5]EEEEE[19,4]FFFF[22,3]GGG[0,0]HH[0,0]I", testTree.tree.getStructureAsString())
|
||||
XCTAssertTrue(testTree.nodes[6] === testTree.tree.getRoot())
|
||||
|
@ -156,7 +156,7 @@ class SplayTreeTests: XCTestCase {
|
|||
let testTree = self.sampleTree
|
||||
// check the case 1 of rangeDelete
|
||||
self.removeNodes(testTree.nodes, from: 3, to: 6)
|
||||
testTree.tree.deleteRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[7])
|
||||
testTree.tree.removeRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[7])
|
||||
XCTAssertTrue(testTree.nodes[7] === testTree.tree.getRoot())
|
||||
XCTAssertTrue(testTree.nodes[2] === testTree.tree.getRoot()?.getLeft())
|
||||
XCTAssertEqual(testTree.nodes[7].getWeight(), 9)
|
||||
|
@ -170,7 +170,7 @@ class SplayTreeTests: XCTestCase {
|
|||
testTree.tree.splayNode(testTree.nodes[2])
|
||||
// check the case 2 of rangeDelete
|
||||
self.removeNodes(testTree.nodes, from: 3, to: 7)
|
||||
testTree.tree.deleteRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[8])
|
||||
testTree.tree.removeRange(leftBoundary: testTree.nodes[2], rightBoundary: testTree.nodes[8])
|
||||
XCTAssertTrue(testTree.nodes[8] === testTree.tree.getRoot())
|
||||
XCTAssertTrue(testTree.nodes[2] === testTree.tree.getRoot()?.getLeft())
|
||||
XCTAssertEqual(testTree.nodes[8].getWeight(), 7)
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
CE7B997828E178EF00D56198 /* PrimitiveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997128E1750000D56198 /* PrimitiveTests.swift */; };
|
||||
CE7B997A28E2820200D56198 /* RGATreeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997928E2820200D56198 /* RGATreeList.swift */; };
|
||||
CE7B997C28E2DB5000D56198 /* RGATreeListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997B28E2DB5000D56198 /* RGATreeListTests.swift */; };
|
||||
CE7B997E28E2FD7000D56198 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997D28E2FD7000D56198 /* Array.swift */; };
|
||||
CE7B997E28E2FD7000D56198 /* CRDTArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B997D28E2FD7000D56198 /* CRDTArray.swift */; };
|
||||
CE7B998628E520D600D56198 /* CRDTArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7B998528E520D600D56198 /* CRDTArrayTests.swift */; };
|
||||
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22EE28C9E85900432DE5 /* Change.swift */; };
|
||||
CE8C22F328C9E87800432DE5 /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F228C9E87800432DE5 /* Object.swift */; };
|
||||
CE8C22F528C9E88500432DE5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F428C9E88500432DE5 /* Operation.swift */; };
|
||||
|
@ -78,7 +79,8 @@
|
|||
CE7B997528E1773A00D56198 /* GRPCTypeAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRPCTypeAlias.swift; sourceTree = "<group>"; };
|
||||
CE7B997928E2820200D56198 /* RGATreeList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RGATreeList.swift; sourceTree = "<group>"; };
|
||||
CE7B997B28E2DB5000D56198 /* RGATreeListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RGATreeListTests.swift; sourceTree = "<group>"; };
|
||||
CE7B997D28E2FD7000D56198 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
|
||||
CE7B997D28E2FD7000D56198 /* CRDTArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRDTArray.swift; sourceTree = "<group>"; };
|
||||
CE7B998528E520D600D56198 /* CRDTArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CRDTArrayTests.swift; sourceTree = "<group>"; };
|
||||
CE8C22EE28C9E85900432DE5 /* Change.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = "<group>"; };
|
||||
CE8C22F228C9E87800432DE5 /* Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = "<group>"; };
|
||||
CE8C22F428C9E88500432DE5 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
|
||||
|
@ -194,6 +196,7 @@
|
|||
CE3EC97128D4042D009471BC /* CRDTElementTests.swift */,
|
||||
CE7B997128E1750000D56198 /* PrimitiveTests.swift */,
|
||||
CE7B997B28E2DB5000D56198 /* RGATreeListTests.swift */,
|
||||
CE7B998528E520D600D56198 /* CRDTArrayTests.swift */,
|
||||
);
|
||||
path = CRDT;
|
||||
sourceTree = "<group>";
|
||||
|
@ -277,7 +280,7 @@
|
|||
CE3EC96628D30E74009471BC /* CRDTElement.swift */,
|
||||
CE3EC97428D41903009471BC /* Primitive.swift */,
|
||||
CE7B997928E2820200D56198 /* RGATreeList.swift */,
|
||||
CE7B997D28E2FD7000D56198 /* Array.swift */,
|
||||
CE7B997D28E2FD7000D56198 /* CRDTArray.swift */,
|
||||
);
|
||||
path = CRDT;
|
||||
sourceTree = "<group>";
|
||||
|
@ -519,7 +522,7 @@
|
|||
CE8C22F928C9E8CA00432DE5 /* Heap.swift in Sources */,
|
||||
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */,
|
||||
CEEB17E328C84D26004988DD /* yorkie.pb.swift in Sources */,
|
||||
CE7B997E28E2FD7000D56198 /* Array.swift in Sources */,
|
||||
CE7B997E28E2FD7000D56198 /* CRDTArray.swift in Sources */,
|
||||
CE3EC94F28D1922E009471BC /* RedBlackTree.swift in Sources */,
|
||||
CE7B997028E1453E00D56198 /* Strings.swift in Sources */,
|
||||
CE8C230528C9F1BD00432DE5 /* Client.swift in Sources */,
|
||||
|
@ -545,6 +548,7 @@
|
|||
CE7B997C28E2DB5000D56198 /* RGATreeListTests.swift in Sources */,
|
||||
CE3EC97328D40498009471BC /* CRDTElementTests.swift in Sources */,
|
||||
CE3EC96E28D3FFF0009471BC /* TimeTicketTests.swift in Sources */,
|
||||
CE7B998628E520D600D56198 /* CRDTArrayTests.swift in Sources */,
|
||||
96DA809128C5B7B400E2C1DA /* GRPCTests.swift in Sources */,
|
||||
CE3EC94D28D189EF009471BC /* HeapTests.swift in Sources */,
|
||||
CE3EC95228D195E4009471BC /* RedBlackTreeTests.swift in Sources */,
|
||||
|
|
Loading…
Reference in New Issue