1
0
mirror of git://jb55.com/damus synced 2024-09-30 08:50:42 +00:00
damus/damusTests/VideoCacheTests.swift

113 lines
5.9 KiB
Swift
Raw Normal View History

Cache videos This commit implements a simple but functional video cache. It works by providing a method called `maybe_cached_url`, where a video URL can be passed in, and this method will either return the URL of a cached version of this video if available, or the original URL if not. It also downloads new video URLs on the background into the cache folder for use next time. Functional testing ------------------- PASS Device: iPhone 15 simulator iOS: 17.4 Damus: Approximately this commit Setup: - Debug connection - Expiry time locally changed to 5 minutes Steps: 1. Basic functionality 1. Go to a profile with lots of videos 2. Scroll down 3. Filter logs to only logs that start with "Loading video with URL" 4. Check that most videos are being loaded from external URLs. PASS 5. Now restart the app and go to that same profile 6. Scroll down and watch logs. Videos should now be loaded with an internal file URL. PASS 2. Automatic cache refresh after expiry 1. Go to the video-heavy profile, make note of the external URL. 2. Go to a different screen and then come back to that video. Make sure the file was loaded from cache. PASS 3. Now go to a different screen and wait 5 minutes. 4. Come back to the same video. It should be loaded from the external URL. PASS 3. "Clear cache" button functionality 1. Go to the video-heavy profile, make note of the external URL. 2. Go to a different screen and then come back to that video. Make sure the file was loaded from cache. PASS 3. Now quit the app (to ensure file is not in use when trying to delete it) 4. Clear cache in settings 5. Go back to the same video. It should now be loaded from the external URL. PASS Performance testing ----------------------- Device: iPhone 13 mini iOS: 17.3.1 Damus: This commit Baseline: 87de88861adb3b41d73998452e7c876ab5ee06bf Setup: - Debug connection - Expiry time locally changed to 5 minutes - Running on Profile mode, with XCode Instruments Steps: 1. Start recording network activity with XCode Instruments 2. Go to a video-heavy profile (e.g. Julian Figueroa) 3. Scroll down to a specific video (Make sure to scroll through at least 5 videos) 4. Stop recording and measure the "Bytes In" from "Network connections" 5. Repeat this for all test configurations Results: - Baseline (No caching support): 26.74 MiB - This commit (First run, cleared cache): 40.52 MiB - This commit (Second run, cache filled with videos): 8.13 MiB Automated test coverage ------------------------ PASS Device: iPhone 15 simulator iOS: 17.4 Damus: This commit Coverage: - Ran new automated tests multiple times. PASS 3/3 times - Ran all other automated tests. PASS Signed-off-by: Daniel D’Aquino <daniel@daquino.me> Link: 20240411004129.84436-3-daniel@daquino.me Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-11 00:41:54 +00:00
//
// VideoCacheTests.swift
// damusTests
//
// Created by Daniel DAquino on 2024-04-03.
//
import Foundation
import XCTest
@testable import damus
// TODO: Reduce test dependency on external factors such as external URLs.
let TEST_VIDEO_URL = "http://cdn.jb55.com/s/zaps-build.mp4"
let LONG_TEST_EXPIRY_TIME: TimeInterval = 60 * 60 * 24 // A long expiry time for a video (in seconds).
let SHORT_TEST_EXPIRY_TIME: TimeInterval = 15 // A short expiry time for a video (in seconds). Must be as short as possible but large enough to allow some test operations to occur
let CACHE_SAVE_TIME_TIMEOUT: TimeInterval = 8 // How long the test will wait for the cache to save a file (in seconds)
let EXPIRY_TIME_MARGIN: TimeInterval = 3 // The extra time we will wait after expected expiry, to avoid test timing issues. (in seconds)
final class VideoCacheTests: XCTestCase {
func testCachedURLForExistingVideo() throws {
// Create a temporary directory for the cache
let test_cache_directory = FileManager.default.temporaryDirectory.appendingPathComponent("test_video_cache")
// Create a test video file
let original_video_url = URL(string: TEST_VIDEO_URL)!
FileManager.default.createFile(atPath: original_video_url.path, contents: Data(), attributes: nil)
// Create a VideoCache instance with the temporary cache directory
let test_expiry_time: TimeInterval = 10
let video_cache = try VideoCache(cache_url: test_cache_directory, expiry_time: test_expiry_time)!
// Call the maybe_cached_url_for method with the test video URL
let expected_cache_url = video_cache.url_to_cached_url(url: original_video_url)
let maybe_cached_url = try video_cache.maybe_cached_url_for(video_url: original_video_url)
// Assert that the returned URL is the same as the original
XCTAssertEqual(maybe_cached_url, original_video_url, "Returned URL should be the same as the original video URL on the first time we download it")
// Check that next time we get this video, we get the cached URL.
let cached_url_expectation = XCTestExpectation(description: "On second time we get a video, the cached URL should be returned")
let start_time = Date()
while Date().timeIntervalSince(start_time) < CACHE_SAVE_TIME_TIMEOUT {
let maybe_cached_url = try video_cache.maybe_cached_url_for(video_url: original_video_url)
if maybe_cached_url == expected_cache_url {
cached_url_expectation.fulfill()
break
}
sleep(1)
}
wait(for: [cached_url_expectation], timeout: CACHE_SAVE_TIME_TIMEOUT)
// Now wait for the remaining time until the expiry time + a margin
let remaining_time = test_expiry_time + EXPIRY_TIME_MARGIN - Date().timeIntervalSince(start_time)
// Wait for the expiry time to pass
sleep(UInt32(max(remaining_time, 0)))
// Call the periodic_purge method to purge expired video items
video_cache.periodic_purge()
// Call the maybe_cached_url_for method again
let maybe_cached_url_after_expiry = try video_cache.maybe_cached_url_for(video_url: original_video_url)
// Assert that the returned URL is the same as the original video URL, since the cache should have expired.
XCTAssertEqual(maybe_cached_url_after_expiry, original_video_url, "Video cache should expire after expiry time")
// Clean up the temporary files and directory
try FileManager.default.removeItem(at: test_cache_directory)
}
func testClearCache() throws {
// Create a temporary directory for the cache
let test_cache_directory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("test_video_cache")
try FileManager.default.createDirectory(at: test_cache_directory, withIntermediateDirectories: true, attributes: nil)
// Create a test video file
let original_video_url = URL(string: TEST_VIDEO_URL)!
FileManager.default.createFile(atPath: original_video_url.path, contents: Data(), attributes: nil)
// Create a VideoCache instance with the temporary cache directory and a longer expiry time
let expiry_time: TimeInterval = LONG_TEST_EXPIRY_TIME
let video_cache = try VideoCache(cache_url: test_cache_directory, expiry_time: expiry_time)!
// Request the cached URL for the test video to create the cached file
let expected_cache_url = video_cache.url_to_cached_url(url: original_video_url)
let _ = try video_cache.maybe_cached_url_for(video_url: original_video_url)
// Check that next time we get this video, we get the cached URL.
let cached_url_expectation = XCTestExpectation(description: "On second time we get a video, the cached URL should be returned")
let start_time = Date()
while Date().timeIntervalSince(start_time) < CACHE_SAVE_TIME_TIMEOUT {
let maybe_cached_url = try video_cache.maybe_cached_url_for(video_url: original_video_url)
if maybe_cached_url == expected_cache_url {
cached_url_expectation.fulfill()
break
}
sleep(1)
}
wait(for: [cached_url_expectation], timeout: CACHE_SAVE_TIME_TIMEOUT)
// Call the periodic_purge method
DamusCacheManager.shared.clear_cache(damus_state: test_damus_state, completion: {
// Assert that fetching the cached URL after clearing cache will
let maybe_cached_url_after_purge = try? video_cache.maybe_cached_url_for(video_url: original_video_url)
XCTAssertEqual(maybe_cached_url_after_purge, original_video_url)
// Clean up the temporary directory
try? FileManager.default.removeItem(at: test_cache_directory)
})
}
}