Swiftgram/PostboxTests/MessageHistoryTableTests.swift
2016-02-01 03:06:14 +03:00

706 lines
24 KiB
Swift

import Foundation
import UIKit
import XCTest
import Postbox
@testable import Postbox
private let peerId = PeerId(namespace: 1, id: 1)
private let namespace: Int32 = 1
private let authorPeerId = PeerId(namespace: 1, id: 6)
private let peer = TestPeer(id: 6, data: "abc")
private func ==(lhs: [Media], rhs: [Media]) -> Bool {
if lhs.count != rhs.count {
return false
}
for i in 0 ..< lhs.count {
if !lhs[i].isEqual(rhs[i]) {
return false
}
}
return true
}
private enum Entry: Equatable, CustomStringConvertible {
case Message(Int32, Int32, String, [Media])
case Hole(Int32, Int32, Int32)
var description: String {
switch self {
case let .Message(id, timestamp, text, media):
return "Message(\(id), \(timestamp), \(text), \(media))"
case let .Hole(min, max, timestamp):
return "Hole(\(min), \(max), \(timestamp))"
}
}
}
private func ==(lhs: Entry, rhs: Entry) -> Bool {
switch lhs {
case let .Message(lhsId, lhsTimestamp, lhsText, lhsMedia):
switch rhs {
case let .Message(rhsId, rhsTimestamp, rhsText, rhsMedia):
return lhsId == rhsId && lhsTimestamp == rhsTimestamp && lhsText == rhsText && lhsMedia == rhsMedia
case .Hole:
return false
}
case let .Hole(lhsMin, lhsMax, lhsMaxTimestamp):
switch rhs {
case .Message:
return false
case let .Hole(rhsMin, rhsMax, rhsMaxTimestamp):
return lhsMin == rhsMin && lhsMax == rhsMax && lhsMaxTimestamp == rhsMaxTimestamp
}
}
}
private class TestEmbeddedMedia: Media, CustomStringConvertible {
var id: MediaId? { return nil }
var peerIds: [PeerId] = []
let data: String
init(data: String) {
self.data = data
}
required init(decoder: Decoder) {
self.data = decoder.decodeStringForKey("s")
}
func encode(encoder: Encoder) {
encoder.encodeString(self.data, forKey: "s")
}
func isEqual(other: Media) -> Bool {
if let other = other as? TestEmbeddedMedia {
return self.data == other.data
}
return false
}
var description: String {
return "TestEmbeddedMedia(\(self.data))"
}
}
private class TestExternalMedia: Media {
let id: MediaId?
var peerIds: [PeerId] = []
let data: String
init(id: Int64, data: String) {
self.id = MediaId(namespace: namespace, id: id)
self.data = data
}
required init(decoder: Decoder) {
self.id = MediaId(namespace: decoder.decodeInt32ForKey("i.n"), id: decoder.decodeInt64ForKey("i.i"))
self.data = decoder.decodeStringForKey("s")
}
func encode(encoder: Encoder) {
encoder.encodeInt32(self.id!.namespace, forKey: "i.n")
encoder.encodeInt64(self.id!.id, forKey: "i.i")
encoder.encodeString(self.data, forKey: "s")
}
func isEqual(other: Media) -> Bool {
if let other = other as? TestExternalMedia {
return self.id == other.id && self.data == other.data
}
return false
}
var description: String {
return "TestExternalMedia(\(self.id!.id), \(self.data))"
}
}
private class TestPeer: Peer {
let id: PeerId
let data: String
init(id: Int32, data: String) {
self.id = PeerId(namespace: namespace, id: id)
self.data = data
}
required init(decoder: Decoder) {
self.id = PeerId(namespace: decoder.decodeInt32ForKey("i.n"), id: decoder.decodeInt32ForKey("i.i"))
self.data = decoder.decodeStringForKey("s")
}
func encode(encoder: Encoder) {
encoder.encodeInt32(self.id.namespace, forKey: "i.n")
encoder.encodeInt32(self.id.id, forKey: "i.i")
encoder.encodeString(self.data, forKey: "s")
}
func isEqual(other: Peer) -> Bool {
if let other = other as? TestPeer {
return self.id == other.id && self.data == other.data
}
return false
}
var description: String {
return "TestPeer(\(self.id.id), \(self.data))"
}
}
private enum MediaEntry: Equatable {
case Direct(Media, Int)
case MessageReference(Int32)
init(_ entry: DebugMediaEntry) {
switch entry {
case let .Direct(media, referenceCount):
self = .Direct(media, referenceCount)
case let .MessageReference(index):
self = MessageReference(index.id.id)
}
}
}
private func ==(lhs: MediaEntry, rhs: MediaEntry) -> Bool {
switch lhs {
case let .Direct(lhsMedia, lhsReferenceCount):
switch rhs {
case let .Direct(rhsMedia, rhsReferenceCount):
return lhsMedia.isEqual(rhsMedia) && lhsReferenceCount == rhsReferenceCount
case .MessageReference:
return false
}
case let .MessageReference(lhsId):
switch rhs {
case .Direct:
return false
case let .MessageReference(rhsId):
return lhsId == rhsId
}
}
}
class MessageHistoryTableTests: XCTestCase {
var valueBox: ValueBox?
var path: String?
var peerTable: PeerTable?
var globalMessageIdsTable: GlobalMessageIdsTable?
var indexTable: MessageHistoryIndexTable?
var mediaTable: MessageMediaTable?
var mediaCleanupTable: MediaCleanupTable?
var historyTable: MessageHistoryTable?
override class func setUp() {
super.setUp()
declareEncodable(TestEmbeddedMedia.self, f: {TestEmbeddedMedia(decoder: $0)})
declareEncodable(TestExternalMedia.self, f: {TestExternalMedia(decoder: $0)})
declareEncodable(TestPeer.self, f: {TestPeer(decoder: $0)})
}
override func setUp() {
super.setUp()
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
path = NSTemporaryDirectory().stringByAppendingString("\(randomId)")
self.valueBox = SqliteValueBox(basePath: path!)
self.globalMessageIdsTable = GlobalMessageIdsTable(valueBox: self.valueBox!, tableId: 5, namespace: namespace)
self.indexTable = MessageHistoryIndexTable(valueBox: self.valueBox!, tableId: 1, globalMessageIdsTable: self.globalMessageIdsTable!)
self.mediaCleanupTable = MediaCleanupTable(valueBox: self.valueBox!, tableId: 3)
self.mediaTable = MessageMediaTable(valueBox: self.valueBox!, tableId: 2, mediaCleanupTable: self.mediaCleanupTable!)
self.historyTable = MessageHistoryTable(valueBox: self.valueBox!, tableId: 4, messageHistoryIndexTable: self.indexTable!, messageMediaTable: self.mediaTable!)
self.peerTable = PeerTable(valueBox: self.valueBox!, tableId: 6)
self.peerTable!.set(peer)
}
override func tearDown() {
super.tearDown()
self.historyTable = nil
self.indexTable = nil
self.mediaTable = nil
self.mediaCleanupTable = nil
self.peerTable = nil
self.valueBox = nil
let _ = try? NSFileManager.defaultManager().removeItemAtPath(path!)
self.path = nil
}
private func addMessage(id: Int32, _ timestamp: Int32, _ text: String = "", _ media: [Media] = []) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
self.historyTable!.addMessages([StoreMessage(id: MessageId(peerId: peerId, namespace: namespace, id: id), timestamp: timestamp, authorId: authorPeerId, text: text, attributes: [], media: media)], location: .Random, operationsByPeerId: &operationsByPeerId)
//print("\(operationsByPeerId[peerId]!)")
}
private func addHole(id: Int32) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
self.historyTable!.addHoles([MessageId(peerId: peerId, namespace: namespace, id: id)], operationsByPeerId: &operationsByPeerId)
}
private func removeMessages(ids: [Int32]) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
self.historyTable!.removeMessages(ids.map({ MessageId(peerId: peerId, namespace: namespace, id: $0) }), operationsByPeerId: &operationsByPeerId)
//print("\(operationsByPeerId[peerId]!)")
}
private func fillHole(id: Int32, _ fillType: HoleFillType, _ messages: [(Int32, Int32, String, [Media])]) {
var operationsByPeerId: [PeerId: [MessageHistoryOperation]] = [:]
self.historyTable!.fillHole(MessageId(peerId: peerId, namespace: namespace, id: id), fillType: fillType, messages: messages.map({ StoreMessage(id: MessageId(peerId: peerId, namespace: namespace, id: $0.0), timestamp: $0.1, authorId: authorPeerId, text: $0.2, attributes: [], media: $0.3) }), operationsByPeerId: &operationsByPeerId)
}
private func expectEntries(entries: [Entry]) {
let actualEntries = self.historyTable!.debugList(peerId, peerTable: self.peerTable!).map({ entry -> Entry in
switch entry {
case let .RenderedMessage(message):
if let messagePeer = message.author {
if !peer.isEqual(messagePeer) {
XCTFail("Expected peer \(peer), actual: \(messagePeer)")
}
} else {
XCTFail("Expected peer \(peer), actual: nil")
}
return .Message(message.id.id, message.timestamp, message.text, message.media)
case let .Hole(hole):
return .Hole(hole.min, hole.maxIndex.id.id, hole.maxIndex.timestamp)
}
})
if actualEntries != entries {
XCTFail("Expected\n\(entries)\nActual\n\(actualEntries)")
}
}
private func expectMedia(media: [MediaEntry]) {
let actualMedia = self.mediaTable!.debugList().map({MediaEntry($0)})
if media != actualMedia {
XCTFail("Expected\n\(media)\nActual\n\(actualMedia)")
}
}
private func expectCleanupMedia(media: [Media]) {
let actualMedia = self.mediaCleanupTable!.debugList()
var equal = true
if media.count != actualMedia.count {
equal = false
} else {
for i in 0 ..< media.count {
if !media[i].isEqual(actualMedia[i]) {
equal = false
break
}
}
}
if !equal {
XCTFail("Expected\n\(media)\nActual\n\(actualMedia)")
}
}
func testInsertMessageIntoEmpty() {
addMessage(100, 100, "t100")
addMessage(200, 200, "t200")
expectEntries([.Message(100, 100, "t100", []), .Message(200, 200, "t200", [])])
}
func testInsertMessageIgnoreOverwrite() {
addMessage(100, 100, "t100")
addMessage(100, 200, "t200")
expectEntries([.Message(100, 100, "t100", [])])
}
func testInsertMessageWithEmbeddedMedia() {
addMessage(100, 100, "t100", [TestEmbeddedMedia(data: "abc1")])
expectEntries([.Message(100, 100, "t100", [TestEmbeddedMedia(data: "abc1")])])
expectMedia([])
}
func testInsertMessageWithExternalMedia() {
let media = TestExternalMedia(id: 10, data: "abc1")
addMessage(100, 100, "t100", [media])
expectEntries([.Message(100, 100, "t100", [media])])
expectMedia([.MessageReference(100)])
}
func testUnembedExternalMedia() {
let media = TestExternalMedia(id: 10, data: "abc1")
addMessage(100, 100, "t100", [media])
addMessage(200, 200, "t200", [media])
expectEntries([.Message(100, 100, "t100", [media]), .Message(200, 200, "t200", [media])])
expectMedia([.Direct(media, 2)])
}
func testIgnoreOverrideExternalMedia() {
let media = TestExternalMedia(id: 10, data: "abc1")
let media1 = TestExternalMedia(id: 10, data: "abc2")
addMessage(100, 100, "t100", [media])
addMessage(200, 200, "t200", [media1])
expectEntries([.Message(100, 100, "t100", [media]), .Message(200, 200, "t200", [media])])
expectMedia([.Direct(media, 2)])
}
func testRemoveSingleMessage() {
addMessage(100, 100, "t100", [])
removeMessages([100])
expectEntries([])
expectMedia([])
}
func testRemoveMessageWithEmbeddedMedia() {
let media = TestEmbeddedMedia(data: "abc1")
addMessage(100, 100, "t100", [media])
self.removeMessages([100])
expectEntries([])
expectMedia([])
expectCleanupMedia([media])
}
func testRemoveOnlyReferenceToExternalMedia() {
let media = TestExternalMedia(id: 10, data: "abc1")
addMessage(100, 100, "t100", [media])
removeMessages([100])
expectEntries([])
expectMedia([])
expectCleanupMedia([media])
}
func testRemoveReferenceToExternalMedia() {
let media = TestExternalMedia(id: 10, data: "abc1")
addMessage(100, 100, "t100", [media])
addMessage(200, 200, "t200", [media])
removeMessages([100])
expectEntries([.Message(200, 200, "t200", [media])])
expectMedia([.Direct(media, 1)])
expectCleanupMedia([])
removeMessages([200])
expectEntries([])
expectMedia([])
expectCleanupMedia([media])
}
func testAddHoleToEmpty() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
}
func testAddHoleToFullHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addHole(110)
expectEntries([.Hole(1, Int32.max, Int32.max)])
}
func testAddMessageToFullHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addMessage(90, 90, "m90")
expectEntries([.Hole(1, 89, 90), .Message(90, 90, "m90", []), .Hole(91, Int32.max, Int32.max)])
}
func testAddMessageDividingUpperHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addMessage(90, 90, "m90")
expectEntries([.Hole(1, 89, 90), .Message(90, 90, "m90", []), .Hole(91, Int32.max, Int32.max)])
addMessage(100, 100, "m100")
expectEntries([.Hole(1, 89, 90), .Message(90, 90, "m90", []), .Hole(91, 99, 100), .Message(100, 100, "m100", []), .Hole(101, Int32.max, Int32.max)])
}
func testAddMessageDividingLowerHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addMessage(90, 90, "m90")
expectEntries([.Hole(1, 89, 90), .Message(90, 90, "m90", []), .Hole(91, Int32.max, Int32.max)])
addMessage(80, 80, "m80")
expectEntries([.Hole(1, 79, 80), .Message(80, 80, "m80", []), .Hole(81, 89, 90), .Message(90, 90, "m90", []), .Hole(91, Int32.max, Int32.max)])
}
func testAddMessageOffsettingUpperHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addMessage(90, 90, "m90")
expectEntries([.Hole(1, 89, 90), .Message(90, 90, "m90", []), .Hole(91, Int32.max, Int32.max)])
addMessage(91, 91, "m91")
expectEntries([.Hole(1, 89, 90), .Message(90, 90, "m90", []), .Message(91, 91, "m91", []), .Hole(92, Int32.max, Int32.max)])
}
func testAddMessageOffsettingLowerHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addMessage(90, 90, "m90")
expectEntries([.Hole(1, 89, 90), .Message(90, 90, "m90", []), .Hole(91, Int32.max, Int32.max)])
addMessage(89, 89, "m89")
expectEntries([.Hole(1, 88, 89), .Message(89, 89, "m89", []), .Message(90, 90, "m90", []), .Hole(91, Int32.max, Int32.max)])
}
func testAddMessageOffsettingLeftmostHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addMessage(1, 1, "m1")
expectEntries([.Message(1, 1, "m1", []), .Hole(2, Int32.max, Int32.max)])
}
func testAddMessageRemovingLefmostHole() {
addHole(100)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addMessage(2, 2, "m2")
expectEntries([.Hole(1, 1, 2), .Message(2, 2, "m2", []), .Hole(3, Int32.max, Int32.max)])
addMessage(1, 1, "m1")
expectEntries([.Message(1, 1, "m1", []), .Message(2, 2, "m2", []), .Hole(3, Int32.max, Int32.max)])
}
func testAddHoleLowerThanMessage() {
addMessage(100, 100, "m100")
addHole(1)
expectEntries([.Hole(1, 99, 100), .Message(100, 100, "m100", [])])
}
func testAddHoleHigherThanMessage() {
addMessage(100, 100, "m100")
addHole(200)
expectEntries([.Message(100, 100, "m100", []), .Hole(101, Int32.max, Int32.max)])
}
func testIgnoreHigherHole() {
addHole(200)
expectEntries([.Hole(1, Int32.max, Int32.max)])
addHole(400)
expectEntries([.Hole(1, Int32.max, Int32.max)])
}
func testIgnoreHigherHoleAfterMessage() {
addMessage(100, 100, "m100")
addHole(200)
expectEntries([.Message(100, 100, "m100", []), .Hole(101, Int32.max, Int32.max)])
addHole(400)
expectEntries([.Message(100, 100, "m100", []), .Hole(101, Int32.max, Int32.max)])
}
func testAddHoleBetweenMessages() {
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
addHole(150)
expectEntries([.Message(100, 100, "m100", []), .Hole(101, 199, 200), .Message(200, 200, "m200", [])])
}
func testFillHoleEmpty() {
fillHole(1, .Complete, [])
expectEntries([])
}
func testFillHoleComplete() {
addHole(100)
fillHole(1, .Complete, [(100, 100, "m100", []), (200, 200, "m200", [])])
expectEntries([.Message(100, 100, "m100", []), .Message(200, 200, "m200", [])])
}
func testFillHoleUpperToLowerPartial() {
addHole(100)
fillHole(1, .UpperToLower, [(100, 100, "m100", []), (200, 200, "m200", [])])
expectEntries([.Hole(1, 99, 100), .Message(100, 100, "m100", []), .Message(200, 200, "m200", [])])
}
func testFillHoleUpperToLowerToBounds() {
addHole(100)
fillHole(1, .UpperToLower, [(1, 1, "m1", []), (200, 200, "m200", [])])
expectEntries([.Message(1, 1, "m1", []), .Message(200, 200, "m200", [])])
}
func testFillHoleLowerToUpperToBounds() {
addHole(100)
fillHole(1, .LowerToUpper, [(100, 100, "m100", []), (Int32.max, 200, "m200", [])])
expectEntries([.Message(100, 100, "m100", []), .Message(Int32.max, 200, "m200", [])])
}
func testFillHoleLowerToUpperPartial() {
addHole(100)
fillHole(1, .LowerToUpper, [(100, 100, "m100", []), (200, 200, "m200", [])])
expectEntries([.Message(100, 100, "m100", []), .Message(200, 200, "m200", []), .Hole(201, Int32.max, Int32.max)])
}
func testFillHoleBetweenMessagesUpperToLower() {
addHole(1)
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
fillHole(199, .UpperToLower, [(150, 150, "m150", [])])
expectEntries([.Hole(1, 99, 100), .Message(100, 100, "m100", []), .Hole(101, 149, 150), .Message(150, 150, "m150", []), .Message(200, 200, "m200", []), .Hole(201, Int32.max, Int32.max)])
}
func testFillHoleBetweenMessagesLowerToUpper() {
addHole(1)
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
fillHole(199, .LowerToUpper, [(150, 150, "m150", [])])
expectEntries([.Hole(1, 99, 100), .Message(100, 100, "m100", []), .Message(150, 150, "m150", []), .Hole(151, 199, 200), .Message(200, 200, "m200", []), .Hole(201, Int32.max, Int32.max)])
}
func testFillHoleBetweenMessagesComplete() {
addHole(1)
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
fillHole(199, .Complete, [(150, 150, "m150", [])])
expectEntries([.Hole(1, 99, 100), .Message(100, 100, "m100", []), .Message(150, 150, "m150", []), .Message(200, 200, "m200", []), .Hole(201, Int32.max, Int32.max)])
}
func testFillHoleBetweenMessagesWithMessage() {
addMessage(200, 200, "m200")
addMessage(202, 202, "m202")
addHole(201)
addMessage(201, 201, "m201")
expectEntries([.Message(200, 200, "m200", []), .Message(201, 201, "m201", []), .Message(202, 202, "m202", [])])
}
func testFillHoleWithNoMessagesComplete() {
addMessage(100, 100, "m100")
addHole(1)
fillHole(99, .Complete, [])
expectEntries([.Message(100, 100, "m100", [])])
}
func testFillHoleIgnoreOverMessage() {
addMessage(100, 100, "m100")
addMessage(101, 101, "m101")
fillHole(100, .Complete, [(90, 90, "m90", [])])
expectEntries([.Message(90, 90, "m90", []), .Message(100, 100, "m100", []), .Message(101, 101, "m101", [])])
}
func testFillHoleWithOverflow() {
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
addHole(150)
fillHole(199, .UpperToLower, [(150, 150, "m150", []), (300, 300, "m300", [])])
expectEntries([.Message(100, 100, "m100", []), .Hole(101, 149, 150), .Message(150, 150, "m150", []), .Message(200, 200, "m200", []), .Message(300, 300, "m300", [])])
}
func testIgnoreHoleOverMessageBetweenMessages() {
addMessage(199, 199, "m199")
addMessage(200, 200, "m200")
addHole(200)
expectEntries([.Message(199, 199, "m199", []), .Message(200, 200, "m200", [])])
}
func testMergeHoleAfterDeletingMessage() {
addMessage(100, 100, "m100")
addHole(1)
addHole(200)
expectEntries([.Hole(1, 99, 100), .Message(100, 100, "m100", []), .Hole(101, Int32.max, Int32.max)])
removeMessages([100])
expectEntries([.Hole(1, Int32.max, Int32.max)])
}
func testMergeHoleLowerAfterDeletingMessage() {
addMessage(100, 100, "m100")
addHole(1)
addMessage(200, 200, "m200")
removeMessages([100])
expectEntries([.Hole(1, 199, 200), .Message(200, 200, "m200", [])])
}
func testMergeHoleUpperAfterDeletingMessage() {
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
addHole(300)
removeMessages([200])
expectEntries([.Message(100, 100, "m100", []), .Hole(101, Int32.max, Int32.max)])
}
func testExtendLowerHoleAfterDeletingMessage() {
addMessage(100, 100, "m100")
addHole(100)
removeMessages([100])
expectEntries([.Hole(1, Int32.max, Int32.max)])
}
func testExtendUpperHoleAfterDeletingMessage() {
addMessage(100, 100, "m100")
addHole(101)
removeMessages([100])
expectEntries([.Hole(1, Int32.max, Int32.max)])
}
func testDeleteMessageBelowMessage() {
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
removeMessages([100])
expectEntries([.Message(200, 200, "m200", [])])
}
func testDeleteMessageAboveMessage() {
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
removeMessages([200])
expectEntries([.Message(100, 100, "m100", [])])
}
func testDeleteMessageBetweenMessages() {
addMessage(100, 100, "m100")
addMessage(200, 200, "m200")
addMessage(300, 300, "m300")
removeMessages([200])
expectEntries([.Message(100, 100, "m100", []), .Message(300, 300, "m300", [])])
}
}