This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
219 lines (194 sloc)
8.48 KB
| /* This Source Code Form is subject to the terms of the Mozilla Public | |
| * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| @testable import Client | |
| import Foundation | |
| import Storage | |
| import XCTest | |
| class TestHistory: ProfileTest { | |
| fileprivate func addSite(_ history: BrowserHistory, url: String, title: String, s: Bool = true) { | |
| let site = Site(url: url, title: title) | |
| let visit = SiteVisit(site: site, date: Date.nowMicroseconds()) | |
| XCTAssertEqual(s, history.addLocalVisit(visit).value.isSuccess, "Site added: \(url).") | |
| } | |
| fileprivate func innerCheckSites(_ history: BrowserHistory, callback: @escaping (_ cursor: Cursor<Site>) -> Void) { | |
| // Retrieve the entry | |
| history.getSitesByLastVisit(100).upon { | |
| XCTAssertTrue($0.isSuccess) | |
| callback($0.successValue!) | |
| } | |
| } | |
| fileprivate func checkSites(_ history: BrowserHistory, urls: [String: String], s: Bool = true) { | |
| // Retrieve the entry. | |
| if let cursor = history.getSitesByLastVisit(100).value.successValue { | |
| XCTAssertEqual(cursor.status, CursorStatus.success, "Returned success \(cursor.statusMessage).") | |
| XCTAssertEqual(cursor.count, urls.count, "Cursor has \(urls.count) entries.") | |
| for index in 0..<cursor.count { | |
| let s = cursor[index]! | |
| XCTAssertNotNil(s, "Cursor has a site for entry.") | |
| let title = urls[s.url] | |
| XCTAssertNotNil(title, "Found right URL.") | |
| XCTAssertEqual(s.title, title!, "Found right title.") | |
| } | |
| } else { | |
| XCTFail("Couldn't get cursor.") | |
| } | |
| } | |
| fileprivate func clear(_ history: BrowserHistory) { | |
| XCTAssertTrue(history.clearHistory().value.isSuccess, "History cleared.") | |
| } | |
| fileprivate func checkVisits(_ history: BrowserHistory, url: String) { | |
| let expectation = self.expectation(description: "Wait for history") | |
| history.getSitesByLastVisit(100).upon { result in | |
| XCTAssertTrue(result.isSuccess) | |
| history.getFrecentHistory().getSites(whereURLContains: url, historyLimit: 100, bookmarksLimit: 0).upon { result in | |
| XCTAssertTrue(result.isSuccess) | |
| let cursor = result.successValue! | |
| XCTAssertEqual(cursor.status, CursorStatus.success, "returned success \(cursor.statusMessage)") | |
| // XXX - We don't allow querying much info about visits here anymore, so there isn't a lot to do | |
| expectation.fulfill() | |
| } | |
| } | |
| self.waitForExpectations(timeout: 100, handler: nil) | |
| } | |
| // This is a very basic test. Adds an entry. Retrieves it, and then clears the database | |
| func testHistory() { | |
| withTestProfile { profile -> Void in | |
| let h = profile.history | |
| self.addSite(h, url: "http://url1/", title: "title") | |
| self.addSite(h, url: "http://url1/", title: "title") | |
| self.addSite(h, url: "http://url1/", title: "title 2") | |
| self.addSite(h, url: "https://url2/", title: "title") | |
| self.addSite(h, url: "https://url2/", title: "title") | |
| self.checkSites(h, urls: ["http://url1/": "title 2", "https://url2/": "title"]) | |
| self.checkVisits(h, url: "http://url1/") | |
| self.checkVisits(h, url: "https://url2/") | |
| self.clear(h) | |
| } | |
| } | |
| func testAboutUrls() { | |
| withTestProfile { (profile) -> Void in | |
| let h = profile.history | |
| self.addSite(h, url: "about:home", title: "About Home", s: false) | |
| self.clear(h) | |
| } | |
| } | |
| let NumThreads = 5 | |
| let NumCmds = 10 | |
| func testInsertPerformance() { | |
| withTestProfile { profile -> Void in | |
| let h = profile.history | |
| var j = 0 | |
| self.measure({ () -> Void in | |
| for _ in 0...self.NumCmds { | |
| self.addSite(h, url: "https://someurl\(j).com/", title: "title \(j)") | |
| j += 1 | |
| } | |
| self.clear(h) | |
| }) | |
| } | |
| } | |
| func testGetPerformance() { | |
| withTestProfile { profile -> Void in | |
| let h = profile.history | |
| var j = 0 | |
| var urls = [String: String]() | |
| self.clear(h) | |
| for _ in 0...self.NumCmds { | |
| self.addSite(h, url: "https://someurl\(j).com/", title: "title \(j)") | |
| urls["https://someurl\(j).com/"] = "title \(j)" | |
| j += 1 | |
| } | |
| self.measure({ () -> Void in | |
| self.checkSites(h, urls: urls) | |
| return | |
| }) | |
| self.clear(h) | |
| } | |
| } | |
| // Fuzzing tests. These fire random insert/query/clear commands into the history database from threads. The don't check | |
| // the results. Just look for crashes. | |
| func testRandomThreading() { | |
| withTestProfile { profile -> Void in | |
| let queue = DispatchQueue(label: "My Queue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: nil) | |
| var counter = 0 | |
| let expectation = self.expectation(description: "Wait for history") | |
| for _ in 0..<self.NumThreads { | |
| var history = profile.history as BrowserHistory | |
| self.runRandom(&history, queue: queue, cb: { () -> Void in | |
| counter += 1 | |
| if counter == self.NumThreads { | |
| self.clear(history) | |
| expectation.fulfill() | |
| } | |
| }) | |
| } | |
| self.waitForExpectations(timeout: 10, handler: nil) | |
| } | |
| } | |
| // Same as testRandomThreading, but uses one history connection for all threads | |
| func testRandomThreading2() { | |
| withTestProfile { profile -> Void in | |
| let queue = DispatchQueue(label: "My Queue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: nil) | |
| var history = profile.history as BrowserHistory | |
| var counter = 0 | |
| let expectation = self.expectation(description: "Wait for history") | |
| for _ in 0..<self.NumThreads { | |
| self.runRandom(&history, queue: queue, cb: { () -> Void in | |
| counter += 1 | |
| if counter == self.NumThreads { | |
| self.clear(history) | |
| expectation.fulfill() | |
| } | |
| }) | |
| } | |
| self.waitForExpectations(timeout: 10, handler: nil) | |
| } | |
| } | |
| // Runs a random command on a database. Calls cb when finished. | |
| fileprivate func runRandom(_ history: inout BrowserHistory, cmdIn: Int, cb: @escaping () -> Void) { | |
| var cmd = cmdIn | |
| if cmd < 0 { | |
| cmd = Int(arc4random() % 5) | |
| } | |
| switch cmd { | |
| case 0...1: | |
| let url = "https://randomurl.com/\(arc4random() % 100)" | |
| let title = "title \(arc4random() % 100)" | |
| addSite(history, url: url, title: title) | |
| cb() | |
| case 2...3: | |
| innerCheckSites(history) { cursor in | |
| for site in cursor { | |
| _ = site! | |
| } | |
| } | |
| cb() | |
| default: | |
| history.clearHistory().upon() { success in cb() } | |
| } | |
| } | |
| // Calls numCmds random methods on this database. val is a counter used by this interally (i.e. always pass zero for it). | |
| // Calls cb when finished. | |
| fileprivate func runMultiRandom(_ history: inout BrowserHistory, val: Int, numCmds: Int, cb: @escaping () -> Void) { | |
| if val == numCmds { | |
| cb() | |
| return | |
| } else { | |
| runRandom(&history, cmdIn: -1) { [history] in | |
| var history = history | |
| self.runMultiRandom(&history, val: val+1, numCmds: numCmds, cb: cb) | |
| } | |
| } | |
| } | |
| // Helper for starting a new thread running NumCmds random methods on it. Calls cb when done. | |
| fileprivate func runRandom(_ history: inout BrowserHistory, queue: DispatchQueue, cb: @escaping () -> Void) { | |
| queue.async { [history] in | |
| var history = history | |
| // Each thread creates its own history provider | |
| self.runMultiRandom(&history, val: 0, numCmds: self.NumCmds) { | |
| DispatchQueue.main.async(execute: cb) | |
| } | |
| } | |
| } | |
| } |