fix: replace retained report sessions when re-adding queue items
This commit is contained in:
@@ -348,6 +348,33 @@ int main(int argc, char **argv) {
|
||||
expect(firstRows[0], @"progress", firstURL, 12, true);
|
||||
expect(firstRows[2], @"progress", firstURL, 24, true);
|
||||
expect(secondRows[0], @"ended", secondURL, 100, true);
|
||||
const auto retired = root + "/retired.jsonl", replacement = root + "/replacement.jsonl";
|
||||
{
|
||||
playback::ProgressReports reports;
|
||||
reports.registerFile(firstURL, retired);
|
||||
reports.begin(firstURL);
|
||||
reports.observe(sample(PLAYER_PAUSED, 12));
|
||||
bool accepted = true;
|
||||
try { reports.registerFile(firstURL, replacement); } catch (...) { accepted = false; }
|
||||
require(accepted, "a removed identity can register a fresh reporting session");
|
||||
auto retiredRows = records(retired);
|
||||
require(retiredRows.count == 2, "replacement drains the retired writer");
|
||||
expect(retiredRows[1], @"closed", firstURL, 12, true);
|
||||
NSData *retiredBytes = [NSData dataWithContentsOfFile:nativePath(retired)];
|
||||
reports.observe(sample(PLAYER_PAUSED, 50));
|
||||
reports.begin(firstURL);
|
||||
reports.observe(sample(PLAYER_PAUSED, 24));
|
||||
reports.finish();
|
||||
require([[NSData dataWithContentsOfFile:nativePath(retired)] isEqual:retiredBytes],
|
||||
"replacement never appends to or overwrites the retired report");
|
||||
auto replacementRows = records(replacement);
|
||||
require(replacementRows.count == 2, "fresh session starts only when its media begins");
|
||||
expect(replacementRows[0], @"progress", firstURL, 24, true);
|
||||
expect(replacementRows[1], @"closed", firstURL, 24, true);
|
||||
bool refused = false;
|
||||
try { reports.registerFile(secondURL, retired); } catch (...) { refused = true; }
|
||||
require(refused, "retired report paths remain reserved");
|
||||
}
|
||||
const auto liveSingle = root + "/live-single.jsonl", liveNext = root + "/live-next.jsonl";
|
||||
const auto rollbackPath = root + "/rollback.jsonl";
|
||||
{
|
||||
@@ -357,9 +384,9 @@ int main(int argc, char **argv) {
|
||||
const char *identities[] = {secondURL.c_str()}, *destinations[] = {liveNext.c_str()};
|
||||
reports.registerFiles(identities, destinations, 1);
|
||||
reports.observe(sample(PLAYER_PAUSED, 8));
|
||||
const char *conflictingIDs[] = {"miruro:new:1", secondURL.c_str()};
|
||||
const char *conflictingIDs[] = {firstURL.c_str(), secondURL.c_str()};
|
||||
const auto unused = root + "/unused.jsonl";
|
||||
const char *conflictingPaths[] = {rollbackPath.c_str(), unused.c_str()};
|
||||
const char *conflictingPaths[] = {rollbackPath.c_str(), liveNext.c_str()};
|
||||
bool refused = false;
|
||||
try { reports.registerFiles(conflictingIDs, conflictingPaths, 2); } catch (...) { refused = true; }
|
||||
require(refused, "conflicting later entry rejects the complete registration batch");
|
||||
@@ -370,10 +397,17 @@ int main(int argc, char **argv) {
|
||||
reports.observe(sample(PLAYER_PAUSED, 9));
|
||||
reports.begin("miruro:unregistered:1");
|
||||
reports.observe(sample(PLAYER_PAUSED, 55));
|
||||
refused = false;
|
||||
try { reports.registerFile(firstURL, unused); } catch (...) { refused = true; }
|
||||
require(refused, "enqueue cannot rebind the original single identity after its queue row is removed");
|
||||
NSData *singleBytes = [NSData dataWithContentsOfFile:nativePath(liveSingle)];
|
||||
reports.registerFile(firstURL, unused);
|
||||
reports.begin(firstURL);
|
||||
reports.observe(sample(PLAYER_PAUSED, 11));
|
||||
reports.finish();
|
||||
require([[NSData dataWithContentsOfFile:nativePath(liveSingle)] isEqual:singleBytes],
|
||||
"re-adding the original standalone identity leaves its old report unchanged");
|
||||
auto reboundRows = records(unused);
|
||||
require(reboundRows.count == 2, "standalone identity can start a fresh queue report");
|
||||
expect(reboundRows[0], @"progress", firstURL, 11, true);
|
||||
expect(reboundRows[1], @"closed", firstURL, 11, true);
|
||||
require(reports.error().empty(), "enqueue preserves single reporting and supports owned report revisits");
|
||||
}
|
||||
auto singleRows = records(liveSingle), nextRows = records(liveNext);
|
||||
|
||||
@@ -273,7 +273,7 @@ struct ResolvedPlaybackCheck {
|
||||
require(model.commandError != nil && model.queue.items.count == 4,
|
||||
"registered report-path conflicts reject complete enqueue before files exist")
|
||||
try deliver([(URL(string: "https://www.youtube.com/watch?v=DDDDDDDDDDD")!, directory.appendingPathComponent("valid.jsonl")),
|
||||
(URL(string: "https://example.com/unsupported")!, directory.appendingPathComponent("invalid.jsonl"))])
|
||||
(URL(string: "file:///not-a-stream")!, directory.appendingPathComponent("invalid.jsonl"))])
|
||||
require(model.commandError != nil && model.queue.items.count == 4,
|
||||
"invalid later manifest entry never partially appends or registers reports")
|
||||
try deliver([(URL(string: "https://www.youtube.com/watch?v=DDDDDDDDDDD")!, directory.appendingPathComponent("valid.jsonl"))])
|
||||
@@ -302,7 +302,38 @@ struct ResolvedPlaybackCheck {
|
||||
try await until("original single video remains navigable", model: model) {
|
||||
!model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == first.absoluteString
|
||||
}
|
||||
var retiredReports: [(URL, Data, URL, URL)] = []
|
||||
for (url, oldReport) in [(first, originalReport), (third, thirdReport)] {
|
||||
let oldID = model.queue.items.first(where: { $0.url == url })!.id
|
||||
model.removeQueueItem(oldID)
|
||||
let currentID = model.queue.currentID
|
||||
let freshReport = directory.appendingPathComponent(oldReport.lastPathComponent + ".fresh")
|
||||
try deliver([(url, freshReport)])
|
||||
require(model.commandError == nil && model.queue.items.count == 5 &&
|
||||
model.queue.currentID == currentID, "removed video re-enqueues without interrupting current playback")
|
||||
let retiredBytes = try Data(contentsOf: oldReport)
|
||||
retiredReports.append((oldReport, retiredBytes, freshReport, url))
|
||||
require(!FileManager.default.fileExists(atPath: freshReport.path), "re-added report remains lazy")
|
||||
model.selectQueueItem(model.queue.items.last!.id)
|
||||
try await until("re-added video decodes with a fresh session", model: model) {
|
||||
!model.isBusy && model.state.status == PLAYER_PLAYING &&
|
||||
model.queue.currentURL == url && model.state.position > 0.2
|
||||
}
|
||||
require(model.setPlayback(playing: false), "pause re-added video")
|
||||
try await until("re-added pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
|
||||
}
|
||||
model.shutdown()
|
||||
for (oldReport, retiredBytes, freshReport, url) in retiredReports {
|
||||
let finalBytes = try Data(contentsOf: oldReport)
|
||||
require(finalBytes == retiredBytes, "re-added playback leaves the retired report unchanged")
|
||||
let freshRows = try String(contentsOf: freshReport, encoding: .utf8).split(separator: "\n").map {
|
||||
try JSONSerialization.jsonObject(with: Data($0.utf8)) as! [String: Any]
|
||||
}
|
||||
require(freshRows.contains { $0["event"] as? String == "closed" } &&
|
||||
freshRows.contains { $0["event"] as? String == "progress" } &&
|
||||
freshRows.allSatisfy { $0["media_url"] as? String == url.absoluteString },
|
||||
"re-added video writes its acknowledged progress and close to the fresh report only")
|
||||
}
|
||||
for (file, expected) in [(originalReport, first.absoluteString), (thirdReport, third.absoluteString)] {
|
||||
let rows = try String(contentsOf: file, encoding: .utf8).split(separator: "\n").map {
|
||||
try JSONSerialization.jsonObject(with: Data($0.utf8)) as! [String: Any]
|
||||
|
||||
Reference in New Issue
Block a user