-
Notifications
You must be signed in to change notification settings - Fork 290
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]> Link: [email protected] Signed-off-by: William Casarin <[email protected]>
- Loading branch information
1 parent
76529f6
commit e946c61
Showing
5 changed files
with
127 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// | ||
// VideoCacheTests.swift | ||
// damusTests | ||
// | ||
// Created by Daniel D’Aquino 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) | ||
}) | ||
} | ||
} |