Files
damus/damusTests/NostrNetworkManagerTests/NostrNetworkManagerTests.swift
Daniel D’Aquino de528f3f70 Improve loading speed on home timeline
This commit improves the loading speed for the home timeline (and likely
other areas of the app) by employing various techniques and changes:
- Network EOSE timeout reduced from 10 seconds down to 5 seconds
- Network EOSE does not wait on relays with broken connections
- Offload HomeModel handler event processing to separate tasks to
  avoid a large backlog
- Give SubscriptionManager streamers more fine-grained EOSE signals for
  local optimization
- Only wait for Ndb EOSE on the home timeline for faster loading
- Add logging with time elapsed measurements for easier identification of
  loading problems

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2025-09-24 14:06:10 -07:00

95 lines
4.3 KiB
Swift

//
// NostrNetworkManagerTests.swift
// damus
//
// Created by Daniel D'Aquino on 2025-08-22.
//
import XCTest
@testable import damus
class NostrNetworkManagerTests: XCTestCase {
var damusState: DamusState? = nil
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
damusState = generate_test_damus_state(mock_profile_info: nil)
try! damusState?.nostrNetwork.userRelayList.set(userRelayList: NIP65.RelayList())
damusState?.nostrNetwork.connect()
let notesJSONL = getTestNotesJSONL()
for noteText in notesJSONL.split(separator: "\n") {
let _ = damusState!.ndb.processEvent("[\"EVENT\",\"subid\",\(String(noteText))]")
}
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
damusState = nil
}
func getTestNotesJSONL() -> String {
// Get the path for the test_notes.jsonl file in the same folder as this test file
let testBundle = Bundle(for: type(of: self))
let fileURL = testBundle.url(forResource: "test_notes", withExtension: "jsonl")!
// Load the contents of the file
return try! String(contentsOf: fileURL, encoding: .utf8)
}
func ensureSubscribeGetsAllExpectedNotes(filter: NostrFilter, expectedCount: Int) {
let endOfStream = XCTestExpectation(description: "Stream should receive EOSE")
let gotAtLeastExpectedCount = XCTestExpectation(description: "Stream should receive at least the expected number of items")
var receivedCount = 0
var eventIds: Set<NoteId> = []
Task {
for await item in self.damusState!.nostrNetwork.reader.subscribe(filters: [filter]) {
switch item {
case .event(let lender):
try? lender.borrow { event in
receivedCount += 1
if eventIds.contains(event.id) {
XCTFail("Got duplicate event ID: \(event.id) ")
}
eventIds.insert(event.id)
}
if receivedCount == expectedCount {
gotAtLeastExpectedCount.fulfill()
}
case .eose:
// End of stream, break out of the loop
endOfStream.fulfill()
case .ndbEose:
continue
case .networkEose:
continue
}
}
}
wait(for: [endOfStream, gotAtLeastExpectedCount], timeout: 10.0)
XCTAssertEqual(receivedCount, expectedCount, "Event IDs: \(eventIds.map({ $0.hex() }))")
}
/// Tests to ensure that subscribing gets the correct amount of events
///
/// ## Implementation notes:
///
/// To create a new scenario, `nak` can be used as a reference:
/// 1. `cd` into the folder where the `test_notes.jsonl` file is
/// 2. Run `nak serve --events test_notes.jsonl`
/// 3. On a separate terminal, run `nak` commands with the desired filter against the local relay, and get the line count. Example:
/// ```
/// nak req --kind 1 ws://localhost:10547 | wc -l
/// ```
func testNdbSubscription() {
ensureSubscribeGetsAllExpectedNotes(filter: NostrFilter(kinds: [.text]), expectedCount: 57)
ensureSubscribeGetsAllExpectedNotes(filter: NostrFilter(authors: [Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!]), expectedCount: 22)
ensureSubscribeGetsAllExpectedNotes(filter: NostrFilter(kinds: [.boost], referenced_ids: [NoteId(hex: "64b26d0a587f5f894470e1e4783756b4d8ba971226de975ee30ac1b69970d5a1")!]), expectedCount: 5)
ensureSubscribeGetsAllExpectedNotes(filter: NostrFilter(kinds: [.text, .boost, .zap], referenced_ids: [NoteId(hex: "64b26d0a587f5f894470e1e4783756b4d8ba971226de975ee30ac1b69970d5a1")!], limit: 500), expectedCount: 5)
ensureSubscribeGetsAllExpectedNotes(filter: NostrFilter(kinds: [.text], limit: 10), expectedCount: 10)
ensureSubscribeGetsAllExpectedNotes(filter: NostrFilter(kinds: [.text], until: UInt32(Date.now.timeIntervalSince1970), limit: 10), expectedCount: 10)
}
}