Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Commit

Permalink
Merge pull request #812 from corona-warn-app/hotfix/pruning-2
Browse files Browse the repository at this point in the history
Hotfix/pruning 2
  • Loading branch information
haosap authored Jun 30, 2020
2 parents ac1e8de + c4d88ee commit ad720e3
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 18 deletions.
33 changes: 22 additions & 11 deletions src/xcode/ENA/ENA/Source/Workers/TracingStatusHistory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,20 @@ extension Array where Element == TracingStatusEntry {
return copy
}

struct PrunedEntries {
let relevant: [TracingStatusEntry]
let lastIrrelevant: TracingStatusEntry?
}

// MARK: - Prune stale elements older than 14 days
/// Clean up `[TracingStatusEntry]` so we do not store entries past the threshold (14 days)
///
/// - parameter threshold: Max seconds entries can be in the past for. Defaults to 14 days
private func pruned(with threshold: TimeInterval = Self.maxStoredSeconds) -> TracingStatusHistory {
private func pruned(with threshold: TimeInterval = Self.maxStoredSeconds) -> PrunedEntries {
let maxPast = Date().addingTimeInterval(-threshold)
let relevantEntries = filter { $0.date > maxPast }
let irrelevantEntries = filter { $0.date <= maxPast }
if let lastIrrelevantEntry = irrelevantEntries.last {
return [lastIrrelevantEntry] + relevantEntries
}
return relevantEntries
return .init(relevant: relevantEntries, lastIrrelevant: irrelevantEntries.last)
}

// MARK: - Check Tracing History for Risk Calculation
Expand Down Expand Up @@ -106,27 +108,37 @@ extension Array where Element == TracingStatusEntry {
// In order to have a minimal set of changes for hotfix #1 we hard-code
// the precondition (self is pruned) here and have the old, tested code
// stay the same in _getContinuousEnabledInterval.
pruned()._getContinuousEnabledInterval(since: since)
let prunedEntries = pruned()

let intervalForRelevantEntries = prunedEntries.relevant._getContinuousEnabledInterval(since: since)

let oldestRelevantDate = prunedEntries.relevant.first?.date ?? since
if prunedEntries.lastIrrelevant?.on == true {
let now = since
let earliestPotentialDate = now.addingTimeInterval(-Self.maxStoredSeconds)
let delta = oldestRelevantDate.timeIntervalSince(earliestPotentialDate)
return intervalForRelevantEntries + delta
}

return intervalForRelevantEntries
}

private func _getContinuousEnabledInterval(since: Date = Date()) -> TimeInterval {
// self is pruned
guard !isEmpty else {
return .zero
}

var prevDate = since
// Assume pruned array
let sum = reversed().reduce(.zero) { acc, next -> TimeInterval in
return reversed().reduce(.zero) { acc, next -> TimeInterval in
if next.on {
let sum = acc + prevDate.timeIntervalSince(next.date)
prevDate = next.date
return sum
}
prevDate = next.date
return acc
}

return sum
}

// MARK: - Constants for Tracing
Expand All @@ -139,5 +151,4 @@ extension Array where Element == TracingStatusEntry {
static let maxStoredDays = 14
/// The maximum count of seconds to keep tracing history for
static var maxStoredSeconds: TimeInterval { TimeInterval(maxStoredDays * 24 * 60 * 60) }

}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ final class TracingStatusHistoryTests: XCTestCase {

history = history.consumingState(goodState, date)

XCTAssertEqual(history.countEnabledDays(), 15)
XCTAssertEqual(history.countEnabledDays(), 14)
}

func testIfTracingActiveForThresholdDuration_EnabledClosePast() throws {
Expand Down Expand Up @@ -197,17 +197,88 @@ final class TracingStatusHistoryTests: XCTestCase {
XCTAssertEqual(history.countEnabledHours(), 1)
}

func testEnabledHoursCount_Complex() throws {
func testEnabledHoursCount_WithTheFirstEntryBeingInDistantPast() throws {
var history = TracingStatusHistory()
let goodState = ExposureManagerState(authorized: true, enabled: true, status: .active)
let badState = ExposureManagerState(authorized: true, enabled: false, status: .active)

history = history.consumingState(goodState, Date().addingTimeInterval(.init(days: -15)))
history = history.consumingState(badState, Date().addingTimeInterval(.init(days: -10))) // active for 5 days
history = history.consumingState(goodState, Date().addingTimeInterval(.init(days: -1))) // inactive for 9 days
history = history.consumingState(badState, Date().addingTimeInterval(.init(hours: -1))) // active for 23 hours
let now = Date()

history = history.consumingState(goodState, now.addingTimeInterval(.init(days: -100)))
history = history.consumingState(badState, now.addingTimeInterval(.init(days: -10))) // stop being active after 4 days

XCTAssertEqual(history.countEnabledHours(since: now), 96)
XCTAssertEqual(history.countEnabledDays(since: now), 4)
}


func testEnabledHoursCount_Complex() {
var history = TracingStatusHistory()
let goodState = ExposureManagerState(authorized: true, enabled: true, status: .active)
let badState = ExposureManagerState(authorized: true, enabled: false, status: .active)

let now = Date()

history = history.consumingState(goodState, now.addingTimeInterval(.init(days: -15)))
history = history.consumingState(badState, now.addingTimeInterval(.init(days: -10))) // active for 5 days (where one day is 'irrelevant'
history = history.consumingState(goodState, now.addingTimeInterval(.init(days: -1))) // inactive for 9 days
history = history.consumingState(badState, now.addingTimeInterval(.init(hours: -1))) // active for 23 hours

XCTAssertEqual(history.countEnabledHours(since: now), 24 * 4 + 23)
XCTAssertEqual(history.countEnabledDays(since: now), 4)
}

func testGetEnabledInterval_Accumulator_Good() {
let now = Date()

let history: TracingStatusHistory = [
.init(on: true, date: now.addingTimeInterval(.init(days: -10))),
.init(on: true, date: now.addingTimeInterval(.init(days: -5)))
]

XCTAssertEqual(history.countEnabledHours(since: now), 24 * 10)
XCTAssertEqual(history.countEnabledDays(since: now), 10)
}

func testGetEnabledInterval_Accumulator_Good_2() {
let now = Date()

let history: TracingStatusHistory = [
.init(on: true, date: now.addingTimeInterval(.init(days: -10))),
.init(on: true, date: now.addingTimeInterval(.init(days: -5))),
.init(on: true, date: now.addingTimeInterval(.init(days: -3))),
.init(on: true, date: now.addingTimeInterval(.init(days: -1)))
]

XCTAssertEqual(history.countEnabledHours(since: now), 24 * 10)
XCTAssertEqual(history.countEnabledDays(since: now), 10)
}

func testGetEnabledInterval_Accumulator_Good_3() {
let now = Date()

let history: TracingStatusHistory = [
.init(on: true, date: now.addingTimeInterval(.init(days: -10))),
.init(on: false, date: now.addingTimeInterval(.init(days: -5))), // Active for 5 days
.init(on: true, date: now.addingTimeInterval(.init(days: -2))) // Inactive for 3 days
// Active for 2 more days
]

XCTAssertEqual(history.countEnabledHours(since: now), 24 * 7)
XCTAssertEqual(history.countEnabledDays(since: now), 7)
}

func testGetEnabledInterval_Accumulator_Bad() {
let now = Date()

let history: TracingStatusHistory = [
.init(on: false, date: now.addingTimeInterval(.init(days: -10))),
.init(on: false, date: now.addingTimeInterval(.init(days: -5))),
.init(on: true, date: now.addingTimeInterval(.init(days: -2)))
]

XCTAssertEqual(history.countEnabledHours(), 24 * 5 + 23)
XCTAssertEqual(history.countEnabledHours(since: now), 24 * 2)
XCTAssertEqual(history.countEnabledDays(since: now), 2)
}
}

Expand Down

0 comments on commit ad720e3

Please sign in to comment.