Podfile tweaks to speed up sim builds, unit tests & minor bug fix

Added a patch to the Podfile to avoid rsync'ing and signing WebRTC-lib for simulator builds shaving off 10+ seconds of build time per target due to the sheer size of the WebRTC debug framework
Added some basic unit tests to validate the current search behaviour
Fixed some buggy search behaviours
This commit is contained in:
Morgan Pretty 2023-07-04 17:09:50 +10:00
parent 3151aa8901
commit 0225f436bd
10 changed files with 403 additions and 26 deletions

10
Podfile
View File

@ -101,6 +101,7 @@ end
# Actions to perform post-install
post_install do |installer|
set_minimum_deployment_target(installer)
avoid_rsync_webrtc_if_unchanged(installer)
end
def set_minimum_deployment_target(installer)
@ -110,3 +111,12 @@ def set_minimum_deployment_target(installer)
end
end
end
# This function patches the Cocoapods 'Embed Frameworks' script to avoid running rsync
# for the WebRTC-lib framework in simulator builds if it has already been copied over
# because due to the size it can take over 10 seconds to embed, and gets embeded in
# each target on every build regardless of whether there were changes, drastically
# increasing the length of the build
def avoid_rsync_webrtc_if_unchanged(installer)
system('find "./Pods/Target Support Files" -name "*-frameworks.sh" -exec patch -p0 -i ./Scripts/skip_web_rtc_re_rsync.patch {} \;')
end

View File

@ -204,6 +204,6 @@ SPEC CHECKSUMS:
YYImage: f1ddd15ac032a58b78bbed1e012b50302d318331
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
PODFILE CHECKSUM: 68799237a4dc046f5ac25c573af03b559f5b10c4
PODFILE CHECKSUM: dcca0c4ad69b14cbc2d6ba49f9d690b239828e6d
COCOAPODS: 1.12.1

View File

@ -0,0 +1,12 @@
@@ -41,0 +41,11 @@
+ # Skip the rsync step for the WebRTC-lib in simulator builds
+ if [[ "$PLATFORM_NAME" == "iphonesimulator" ]] && [[ "$source" == *WebRTC.framework* ]]; then
+ if [[ -f "${source}/../already_rsynced.nonce" ]]; then
+ echo "Already rsynced WebRTC, skipping"
+ return 0
+ fi
+
+ echo "About to rsync a simulator WebRTC, creating nonce to prevent future rsyncing"
+ touch "${source}/../already_rsynced.nonce"
+ fi
+

View File

@ -696,6 +696,7 @@
FD716E6C28505E1C00C96BF4 /* MessageRequestsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD716E6B28505E1C00C96BF4 /* MessageRequestsViewModel.swift */; };
FD716E7128505E5200C96BF4 /* MessageRequestsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD716E7028505E5100C96BF4 /* MessageRequestsCell.swift */; };
FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF127BF6BA200510D0C /* Data+Utilities.swift */; };
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */; };
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.swift */; };
FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; };
FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; };
@ -1828,6 +1829,7 @@
FD716E692850327900C96BF4 /* EndCallMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndCallMode.swift; sourceTree = "<group>"; };
FD716E6B28505E1C00C96BF4 /* MessageRequestsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsViewModel.swift; sourceTree = "<group>"; };
FD716E7028505E5100C96BF4 /* MessageRequestsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestsCell.swift; sourceTree = "<group>"; };
FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModelSpec.swift; sourceTree = "<group>"; };
FD7728952849E7E90018502F /* String+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utilities.swift"; sourceTree = "<group>"; };
FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = "<group>"; };
@ -3316,10 +3318,10 @@
B8DE1FB226C22F1F0079C9CE /* Calls */,
C32C5BCB256DC818003C73A2 /* Database */,
C300A5BB2554AFFB00555489 /* Messages */,
C300A5F02554B08500555489 /* Sending & Receiving */,
C352A2F325574B3300338F3E /* Jobs */,
C3A7215C2558C0AC0043A11F /* File Server */,
C3A721332558BDDF0043A11F /* Open Groups */,
C300A5F02554B08500555489 /* Sending & Receiving */,
FD8ECF7529340F4800C0D1BB /* SessionUtil */,
FD3E0C82283B581F002A425C /* Shared Models */,
C3BBE0B32554F0D30050F1E3 /* Utilities */,
@ -3994,6 +3996,14 @@
path = Views;
sourceTree = "<group>";
};
FD7692F52A53A2C7000E4B70 /* Shared Models */ = {
isa = PBXGroup;
children = (
FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */,
);
path = "Shared Models";
sourceTree = "<group>";
};
FD7728A1284F0DF50018502F /* Message Handling */ = {
isa = PBXGroup;
children = (
@ -4224,6 +4234,7 @@
FD3C906527E416A200CD579F /* Contacts */,
FDC4389827BA001800C60D73 /* Open Groups */,
FD3C906B27E43C2400CD579F /* Sending & Receiving */,
FD7692F52A53A2C7000E4B70 /* Shared Models */,
FD3C906827E417B100CD579F /* Utilities */,
FD8ECF802934385900C0D1BB /* LibSessionUtil */,
);
@ -6391,6 +6402,7 @@
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */,
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */,

View File

@ -1979,8 +1979,7 @@ extension ConversationVC:
self?.showInputAccessoryView()
})
self.inputAccessoryView?.isHidden = true
self.inputAccessoryView?.alpha = 0
self.hideInputAccessoryView()
Modal.setupForIPadIfNeeded(actionSheet, targetView: self.view)
self.present(actionSheet, animated: true)
}

View File

@ -106,6 +106,7 @@ class MessageRequestsViewController: BaseVC, SessionUtilRespondingViewController
result.translatesAutoresizingMaskIntoConstraints = false
result.setTitle("MESSAGE_REQUESTS_CLEAR_ALL".localized(), for: .normal)
result.addTarget(self, action: #selector(clearAllTapped), for: .touchUpInside)
result.accessibilityIdentifier = "Clear all"
return result
}()

View File

@ -1112,19 +1112,22 @@ public extension SessionThreadViewModel {
/// Step 2 - Separate any words outside of quotes
/// Step 3 - Join the different search term parts with 'OR" (include results for each individual term)
/// Step 4 - Append a wild-card character to the final word (as long as the last word doesn't end in a quote)
return standardQuotes(searchTerm)
.split(separator: "\"")
.enumerated()
.flatMap { index, value -> [String] in
guard index % 2 == 1 else {
return String(value)
.split(separator: " ")
.map { "\"\(String($0))\"" }
}
return ["\"\(value)\""]
}
.filter { !$0.isEmpty }
let normalisedTerm: String = standardQuotes(searchTerm)
guard let regex = try? NSRegularExpression(pattern: "[^\\s\"']+|\"([^\"]*)\"") else {
// Fallback to removing the quotes and just splitting on spaces
return normalisedTerm
.replacingOccurrences(of: "\"", with: "")
.split(separator: " ")
.map { "\"\($0)\"" }
.filter { !$0.isEmpty }
}
return regex
.matches(in: normalisedTerm, range: NSRange(location: 0, length: normalisedTerm.count))
.compactMap { Range($0.range, in: normalisedTerm) }
.map { normalisedTerm[$0].trimmingCharacters(in: CharacterSet(charactersIn: "\"")) }
.map { "\"\($0)\"" }
}
static func standardQuotes(_ term: String) -> String {
@ -1155,15 +1158,17 @@ public extension SessionThreadViewModel {
/// There are cases where creating a pattern can fail, we want to try and recover from those cases
/// by failling back to simpler patterns if needed
let maybePattern: FTS5Pattern? = (try? db.makeFTS5Pattern(rawPattern: rawPattern, forTable: table))
.defaulting(
to: (try? db.makeFTS5Pattern(rawPattern: fallbackTerm, forTable: table))
.defaulting(to: FTS5Pattern(matchingAnyTokenIn: fallbackTerm))
)
guard let pattern: FTS5Pattern = maybePattern else { throw StorageError.invalidSearchPattern }
return pattern
return try {
if let pattern: FTS5Pattern = try? db.makeFTS5Pattern(rawPattern: rawPattern, forTable: table) {
return pattern
}
if let pattern: FTS5Pattern = try? db.makeFTS5Pattern(rawPattern: fallbackTerm, forTable: table) {
return pattern
}
return try FTS5Pattern(matchingAnyTokenIn: fallbackTerm) ?? { throw StorageError.invalidSearchPattern }()
}()
}
static func messagesQuery(userPublicKey: String, pattern: FTS5Pattern) -> AdaptedFetchRequest<SQLRequest<SessionThreadViewModel>> {

View File

@ -0,0 +1,334 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import Quick
import Nimble
import SessionUtilitiesKit
@testable import SessionMessagingKit
class SessionThreadViewModelSpec: QuickSpec {
public struct TestMessage: Codable, Equatable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
public static var databaseTableName: String { "testMessage" }
public typealias Columns = CodingKeys
public enum CodingKeys: String, CodingKey, ColumnExpression {
case body
}
public let body: String
}
// MARK: - Spec
override func spec() {
describe("a SessionThreadViewModel") {
var mockStorage: Storage!
beforeEach {
mockStorage = SynchronousStorage(
customWriter: try! DatabaseQueue()
)
mockStorage.write { db in
try db.create(table: TestMessage.self) { t in
t.column(.body, .text).notNull()
}
try db.create(virtualTable: TestMessage.fullTextSearchTableName, using: FTS5()) { t in
t.synchronize(withTable: TestMessage.databaseTableName)
t.tokenizer = .porter(wrapping: .unicode61())
t.column(TestMessage.Columns.body.name)
}
}
}
// MARK: - when processing a search term
context("when processing a search term") {
// MARK: -- correctly generates a safe search term
it("correctly generates a safe search term") {
expect(SessionThreadViewModel.searchSafeTerm("Test")).to(equal("\"Test\""))
}
// MARK: -- standardises odd quote characters
it("standardises odd quote characters") {
expect(SessionThreadViewModel.standardQuotes("\"")).to(equal("\""))
expect(SessionThreadViewModel.standardQuotes("")).to(equal("\""))
expect(SessionThreadViewModel.standardQuotes("")).to(equal("\""))
}
// MARK: -- splits on the space character
it("splits on the space character") {
expect(SessionThreadViewModel.searchTermParts("Test Message"))
.to(equal([
"\"Test\"",
"\"Message\""
]))
}
// MARK: -- surrounds each split term with quotes
it("surrounds each split term with quotes") {
expect(SessionThreadViewModel.searchTermParts("Test Message"))
.to(equal([
"\"Test\"",
"\"Message\""
]))
}
// MARK: -- keeps words within quotes together
it("keeps words within quotes together") {
expect(SessionThreadViewModel.searchTermParts("This \"is a Test\" Message"))
.to(equal([
"\"This\"",
"\"is a Test\"",
"\"Message\""
]))
expect(SessionThreadViewModel.searchTermParts("\"This is\" a Test Message"))
.to(equal([
"\"This is\"",
"\"a\"",
"\"Test\"",
"\"Message\""
]))
expect(SessionThreadViewModel.searchTermParts("\"This is\" \"a Test\" Message"))
.to(equal([
"\"This is\"",
"\"a Test\"",
"\"Message\""
]))
expect(SessionThreadViewModel.searchTermParts("\"This is\" a \"Test Message\""))
.to(equal([
"\"This is\"",
"\"a\"",
"\"Test Message\""
]))
expect(SessionThreadViewModel.searchTermParts("\"This is\"\" a \"Test Message"))
.to(equal([
"\"This is\"",
"\" a \"",
"\"Test\"",
"\"Message\""
]))
}
// MARK: -- keeps words within weird quotes together
it("keeps words within weird quotes together") {
expect(SessionThreadViewModel.searchTermParts("This ”is a Test“ Message"))
.to(equal([
"\"This\"",
"\"is a Test\"",
"\"Message\""
]))
}
// MARK: -- removes extra whitespace
it("removes extra whitespace") {
expect(SessionThreadViewModel.searchTermParts(" Test Message "))
.to(equal([
"\"Test\"",
"\"Message\""
]))
}
}
// MARK: - when searching
context("when searching") {
beforeEach {
mockStorage.write { db in
try TestMessage(body: "Test").insert(db)
try TestMessage(body: "Test123").insert(db)
try TestMessage(body: "Test234").insert(db)
try TestMessage(body: "Test Test123").insert(db)
try TestMessage(body: "Test Test123 Test234").insert(db)
try TestMessage(body: "Test Test234").insert(db)
try TestMessage(body: "Test Test234 Test123").insert(db)
try TestMessage(body: "This is a Test Message").insert(db)
try TestMessage(body: "is a Message This Test").insert(db)
try TestMessage(body: "this message is a test").insert(db)
try TestMessage(
body: "This content is something which includes a combination of test words found in another message"
)
.insert(db)
try TestMessage(body: "Do test messages contain content?").insert(db)
try TestMessage(body: "Is messaging awesome?").insert(db)
}
}
// MARK: -- returns results
it("returns results") {
let results = mockStorage.read { db in
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
db,
searchTerm: "Message",
forTable: TestMessage.self
)
return try SQLRequest<TestMessage>(literal: """
SELECT *
FROM testMessage
JOIN testMessage_fts ON (
testMessage_fts.rowId = testMessage.rowId AND
testMessage_fts.body MATCH \(pattern)
)
""").fetchAll(db)
}
expect(results)
.to(equal([
TestMessage(body: "This is a Test Message"),
TestMessage(body: "is a Message This Test"),
TestMessage(body: "this message is a test"),
TestMessage(body: "This content is something which includes a combination of test words found in another message"),
TestMessage(body: "Do test messages contain content?"),
TestMessage(body: "Is messaging awesome?")
]))
}
// MARK: -- adds a wildcard to the final part
it("adds a wildcard to the final part") {
let results = mockStorage.read { db in
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
db,
searchTerm: "This mes",
forTable: TestMessage.self
)
return try SQLRequest<TestMessage>(literal: """
SELECT *
FROM testMessage
JOIN testMessage_fts ON (
testMessage_fts.rowId = testMessage.rowId AND
testMessage_fts.body MATCH \(pattern)
)
""").fetchAll(db)
}
expect(results)
.to(equal([
TestMessage(body: "This is a Test Message"),
TestMessage(body: "is a Message This Test"),
TestMessage(body: "this message is a test"),
TestMessage(body: "This content is something which includes a combination of test words found in another message"),
TestMessage(body: "Do test messages contain content?"),
TestMessage(body: "Is messaging awesome?")
]))
}
// MARK: -- does not add a wildcard to other parts
it("does not add a wildcard to other parts") {
let results = mockStorage.read { db in
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
db,
searchTerm: "mes Random",
forTable: TestMessage.self
)
return try SQLRequest<TestMessage>(literal: """
SELECT *
FROM testMessage
JOIN testMessage_fts ON (
testMessage_fts.rowId = testMessage.rowId AND
testMessage_fts.body MATCH \(pattern)
)
""").fetchAll(db)
}
expect(results)
.to(beEmpty())
}
// MARK: -- finds similar words without the wildcard due to the porter tokenizer
it("finds similar words without the wildcard due to the porter tokenizer") {
let results = mockStorage.read { db in
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
db,
searchTerm: "message z",
forTable: TestMessage.self
)
return try SQLRequest<TestMessage>(literal: """
SELECT *
FROM testMessage
JOIN testMessage_fts ON (
testMessage_fts.rowId = testMessage.rowId AND
testMessage_fts.body MATCH \(pattern)
)
""").fetchAll(db)
}
expect(results)
.to(equal([
TestMessage(body: "This is a Test Message"),
TestMessage(body: "is a Message This Test"),
TestMessage(body: "this message is a test"),
TestMessage(
body: "This content is something which includes a combination of test words found in another message"
),
TestMessage(body: "Do test messages contain content?"),
TestMessage(body: "Is messaging awesome?")
]))
}
// MARK: -- finds results containing the words regardless of the order
it("finds results containing the words regardless of the order") {
let results = mockStorage.read { db in
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
db,
searchTerm: "is a message",
forTable: TestMessage.self
)
return try SQLRequest<TestMessage>(literal: """
SELECT *
FROM testMessage
JOIN testMessage_fts ON (
testMessage_fts.rowId = testMessage.rowId AND
testMessage_fts.body MATCH \(pattern)
)
""").fetchAll(db)
}
expect(results)
.to(equal([
TestMessage(body: "This is a Test Message"),
TestMessage(body: "is a Message This Test"),
TestMessage(body: "this message is a test"),
TestMessage(
body: "This content is something which includes a combination of test words found in another message"
),
TestMessage(body: "Do test messages contain content?"),
TestMessage(body: "Is messaging awesome?")
]))
}
// MARK: -- does not find quoted parts out of order
it("does not find quoted parts out of order") {
let results = mockStorage.read { db in
let pattern: FTS5Pattern = try SessionThreadViewModel.pattern(
db,
searchTerm: "\"this is a\" \"test message\"",
forTable: TestMessage.self
)
return try SQLRequest<TestMessage>(literal: """
SELECT *
FROM testMessage
JOIN testMessage_fts ON (
testMessage_fts.rowId = testMessage.rowId AND
testMessage_fts.body MATCH \(pattern)
)
""").fetchAll(db)
}
expect(results)
.to(equal([
TestMessage(body: "This is a Test Message"),
TestMessage(body: "Do test messages contain content?")
]))
}
}
}
}
}

View File

@ -4,6 +4,8 @@ import Combine
import GRDB
import Quick
import Nimble
import SessionUIKit
import SessionSnodeKit
@testable import Session

View File

@ -4,6 +4,8 @@ import Combine
import GRDB
import Quick
import Nimble
import SessionUIKit
import SessionSnodeKit
@testable import Session