Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pod vector #1

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7254399
add podspecs
cbaker6 May 2, 2020
a83685d
Merge branch 'master'
cbaker6 May 5, 2020
43a2e39
Add sync for all entities. Make uuid and deletedDate public on OCKOut…
cbaker6 May 20, 2020
34bdbc0
Enable query outcome by uuid
cbaker6 May 20, 2020
070095b
Still missing code
cbaker6 May 20, 2020
910f05d
Comment out stuff that's not working
cbaker6 May 20, 2020
38306ee
KnowledgeVector working with all entities
cbaker6 May 21, 2020
b8799f9
Merge branch 'master'
cbaker6 May 21, 2020
1537d03
Fix bug introduced on carePlan where created plans weren't being pers…
cbaker6 May 22, 2020
d527a26
Fix contact deletion during mergeRevision
cbaker6 May 22, 2020
5e4580a
Add new entities to dummyRevision
cbaker6 May 22, 2020
63f493a
Merge branch 'master'
cbaker6 May 22, 2020
f6e3917
Support multiple patients
cbaker6 May 22, 2020
eb62970
Remove old testcase (testAddPatientForAnyPatientBeyondTheFirstPatient…
cbaker6 May 23, 2020
bee81d4
remove extra spacing
cbaker6 May 23, 2020
26f5b76
change naming of test
cbaker6 May 23, 2020
2042cf1
Add more entities to testOverwriteDevice
cbaker6 May 23, 2020
0e92107
Merge branch 'master'
cbaker6 Jun 1, 2020
77935bc
Merge branch 'master'
cbaker6 Jun 4, 2020
a85c2a9
Merge branch 'master'
cbaker6 Jun 5, 2020
2195956
store the rest of the tombstone outcome
cbaker6 Jun 7, 2020
0ae4872
go back to previous version
cbaker6 Jun 7, 2020
335f98f
Add updated logicalClock to tombstoned outcome so it's added to the n…
cbaker6 Jun 7, 2020
f25a597
Add testcase
cbaker6 Jun 8, 2020
58a9bb8
Update test
cbaker6 Jun 8, 2020
29422ab
Fix tombstoning
cbaker6 Jun 8, 2020
efe6278
Fixed test case, but need to change to try during unwrapping to allow…
cbaker6 Jun 8, 2020
ce43cd3
throw instead of force unwrap
cbaker6 Jun 8, 2020
fb1f50a
Cleaned up DummyEndpoint2()
cbaker6 Jun 8, 2020
e48edbb
Update variable names
cbaker6 Jun 8, 2020
c610a45
Better organize DummyEndpoint2
cbaker6 Jun 8, 2020
f411343
Ensure using knowledgeVector in DummyEnpoint2 won't crash and stop fu…
cbaker6 Jun 8, 2020
952fc8c
Make dummyRevision method in DummyEndpoint2 use knowledgeVector for f…
cbaker6 Jun 8, 2020
5a40d99
Fix logical clock update in performVersionedUpdate() along with setti…
cbaker6 Jun 9, 2020
7118cac
Use throw for uuids
cbaker6 Jun 9, 2020
85f94f9
Fix test that was broken from the versioned fix.
cbaker6 Jun 9, 2020
69d3a8a
Apply versioned logicalClock fix
cbaker6 Jun 9, 2020
e25f6da
"next" is being updated somewhere, reverting changes
cbaker6 Jun 10, 2020
8c716ae
revert back
cbaker6 Jun 11, 2020
655ff1f
change placement of logicalClock update
cbaker6 Jun 11, 2020
0cdd450
fix broken
cbaker6 Jun 11, 2020
5f6f0b6
go back to original placement
cbaker6 Jun 11, 2020
6e9c448
All tests pass locally, checking travis again
cbaker6 Jun 11, 2020
81782d9
Merge branch 'master'
cbaker6 Jun 12, 2020
571e5cc
Merge branch 'master'
cbaker6 Jun 16, 2020
d047fab
Merge branch 'master'
cbaker6 Jun 16, 2020
31e092c
Merge branch 'master'
cbaker6 Jun 16, 2020
a2498cd
Update test case for deleting tasks
cbaker6 Jun 16, 2020
318777d
Merge branch 'fix_tombstoning_outcome'
cbaker6 Jun 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CareKit.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Pod::Spec.new do |s|
s.name = 'CareKit'
s.version = '2.0'
s.summary = 'CareKit is an open source software framework for creating apps that help people better understand and manage their health.'
s.homepage = 'https://github.com/carekit-apple/CareKit/'
s.documentation_url = 'https://developer.apple.com/documentation/carekit'
s.screenshots = [ 'https://user-images.githubusercontent.com/51756298/69096972-66de0b00-0a0a-11ea-96f0-4605d04ab396.gif',
'https://user-images.githubusercontent.com/51756298/69107801-7586eb00-0a27-11ea-8aa2-eca687602c76.gif']
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.author = { 'researchandcare.org' => 'https://www.researchandcare.org' }
s.platform = :ios
s.ios.deployment_target = '13.0'
s.swift_versions = '5.0'
s.source = { :git => 'https://github.com/carekit-apple/carekit.git', :tag => s.version.to_s}

s.source_files = 'CareKit/CareKit/**/*'
s.exclude_files = [ 'CareKit/CareKit/**/*.plist', 'OCKCatalog', 'OCKSample', 'DerivedData' ]
s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => "$(SRCROOT)/Pods/**" }
#sp.module_map = 'CareKit/CareKit.modulemap'
s.requires_arc = true
s.frameworks = 'CareKitUI', 'CareKitStore'
s.dependency 'CareKitUI', '2.0'
s.dependency 'CareKitStore', '2.0'

end
21 changes: 21 additions & 0 deletions CareKitStore.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Pod::Spec.new do |s|
s.name = 'CareKitStore'
s.version = '2.0'
s.summary = 'CareKit is an open source software framework for creating apps that help people better understand and manage their health.'
s.homepage = 'https://github.com/carekit-apple/CareKit/'
s.documentation_url = 'https://developer.apple.com/documentation/carekit'
s.screenshots = [ 'https://user-images.githubusercontent.com/51756298/69096972-66de0b00-0a0a-11ea-96f0-4605d04ab396.gif',
'https://user-images.githubusercontent.com/51756298/69107801-7586eb00-0a27-11ea-8aa2-eca687602c76.gif']
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.author = { 'researchandcare.org' => 'https://www.researchandcare.org' }
s.platform = :ios
s.ios.deployment_target = '13.0'
#s.osx.deployment_target = '10.15'
s.swift_versions = '5.0'
s.source = { :git => 'https://github.com/carekit-apple/carekit.git', :tag => s.version.to_s, :submodules => true }

s.source_files = 'CareKitStore/CareKitStore/**/*'
s.exclude_files = 'CareKitStore/CareKitStore/**/*.plist'
s.frameworks = 'HealthKit', 'CoreData'

end
1 change: 1 addition & 0 deletions CareKitStore/CareKitStore/CoreData/OCKStore+Outcomes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ extension OCKStore {
currentOutcomes.forEach {
$0.deletedDate = Date()
$0.values = Set()
$0.logicalClock = Int64(context.clockTime)
}

let newOutcomes = outcomes.map(self.createOutcome)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ extension OCKCoreDataStoreProtocol {
guard let current = currentVersions.first(where: { $0.id == value.id }) else {
throw OCKStoreError.invalidValue(reason: "No matching object could be found for id: \(value.id)")
}
current.logicalClock = Int64(context.clockTime)
let newVersion = addNewVersion(value)
newVersion.previous = current
newVersion.uuid = UUID()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class TestStoreBuildRevisions: XCTestCase {
try store.deleteTasksAndWait([taskA2])
let revision = store.computeRevision(since: store.context.clockTime)

XCTAssert(revision.entities.count == 1)
XCTAssert(revision.entities.count == 2)
XCTAssert(revision.entities.first?.deletedDate != nil)
}

Expand Down
183 changes: 182 additions & 1 deletion CareKitStore/CareKitStoreTests/OCKStore/TestStore+Sync.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class TestStoreSync: XCTestCase {
XCTAssert(localTasks.count == 2)
XCTAssert(localOutcomes.count == 2)
}

func testKeepRemoteTaskWithFirstVersionOfTasks() throws {
peer.automaticallySynchronizes = false
peer.conflictPolicy = .keepRemote
Expand Down Expand Up @@ -267,6 +267,92 @@ class TestStoreSync: XCTestCase {
XCTAssert(localOutcomes == remoteOutcomes)
XCTAssert(localOutcomes.count == 1)
}

func testTombstoningOutcomePushedToRemote() throws {
let dummy2 = DummyEndpoint2()
let testStore = OCKStore(name: "test", type: .inMemory, remote: dummy2)
dummy2.automaticallySynchronizes = false

let schedule = OCKSchedule.mealTimesEachDay(start: Date(), end: nil)
let task = try testStore.addTaskAndWait(OCKTask(id: "A", title: "A", carePlanUUID: nil, schedule: schedule))
let taskUUID = try task.getUUID()

let outcome = try testStore.addOutcomeAndWait(OCKOutcome(taskUUID: taskUUID, taskOccurrenceIndex: 0, values: [OCKOutcomeValue("test")]))
let outcomeUUID = try outcome.getUUID()
XCTAssertNoThrow(try testStore.syncAndWait()) //Sync original outcome

try testStore.deleteOutcomeAndWait(outcome)
XCTAssertNoThrow(try testStore.syncAndWait()) //Sync tombstoned outcome
let latestRevisions = dummy2.revisionsPushedInLastSynch
XCTAssert(latestRevisions.count == 2)

let tombstonedOutcomes = latestRevisions.compactMap{
entity -> OCKOutcome? in
switch entity{
case .outcome(let outcome):
return outcome
default:
return nil
}
}
XCTAssert(tombstonedOutcomes.count == 2)

guard let tombstonedWithSameUUID = try tombstonedOutcomes.filter({try $0.getUUID() == outcomeUUID}).first else{
throw OCKStoreError.invalidValue(reason: "Filter doesn't contain UUID")
}
XCTAssert(tombstonedWithSameUUID.values.isEmpty)
XCTAssert(tombstonedWithSameUUID.deletedDate != nil)

guard let tombstonedWithDifferentUUID = try tombstonedOutcomes.filter({try $0.getUUID() != outcomeUUID}).first else{
throw OCKStoreError.invalidValue(reason: "Filter doesn't contain UUID")
}
XCTAssert(tombstonedWithDifferentUUID.values.count == 1)
XCTAssert(tombstonedWithDifferentUUID.deletedDate != nil)
}

func testUpdateTaskVersionPushedToRemote() throws {
let dummy2 = DummyEndpoint2()
let testStore = OCKStore(name: "test", type: .inMemory, remote: dummy2)
dummy2.automaticallySynchronizes = false

let schedule = OCKSchedule.mealTimesEachDay(start: Date(), end: nil)
var task = try testStore.addTaskAndWait(OCKTask(id: "A", title: "A", carePlanUUID: nil, schedule: schedule))
let taskUUID = try task.getUUID()

XCTAssertNoThrow(try testStore.syncAndWait()) //Sync original outcome

task.instructions = "Updated instructions"
try testStore.updateTaskAndWait(task)
XCTAssertNoThrow(try testStore.syncAndWait()) //Sync updated outcome

let latestRevisions = dummy2.revisionsPushedInLastSynch
XCTAssert(latestRevisions.count == 2)

let versionedTasks = latestRevisions.compactMap{
entity -> OCKTask? in
switch entity{
case .task(let task):
return task
default:
return nil
}
}
XCTAssert(versionedTasks.count == 2)

guard let previousVersionTask = try versionedTasks.filter({try $0.getUUID() == taskUUID}).first else{
throw OCKStoreError.invalidValue(reason: "Filter doesn't contain UUID")
}

guard let currentVersionTask = try versionedTasks.filter({try $0.getUUID() != taskUUID}).first else{
throw OCKStoreError.invalidValue(reason: "Filter doesn't contain UUID")
}
XCTAssert(previousVersionTask.instructions == nil)
XCTAssert(try previousVersionTask.getNextVersionUUID() == currentVersionTask.getUUID())

XCTAssert(currentVersionTask.instructions != nil)
XCTAssert(try currentVersionTask.getPreviousVersionUUID() == taskUUID)
XCTAssertThrowsError(try currentVersionTask.getNextVersionUUID())
}
}

class DummyEndpoint: OCKRemoteSynchronizable {
Expand Down Expand Up @@ -354,3 +440,98 @@ class DummyEndpoint: OCKRemoteSynchronizable {
return revision
}
}

class DummyEndpoint2: OCKRemoteSynchronizable {

var automaticallySynchronizes = true
var shouldSucceed = true
var delay: TimeInterval = 0.0
weak var delegate: OCKRemoteSynchronizationDelegate?

private(set) var timesPullWasCalled = 0
private(set) var timesPushWasCalled = 0
private(set) var timesForcePushed = 0
private(set) var uuid = UUID()
private(set) var dummyKnowledgeVector:OCKRevisionRecord.KnowledgeVector? = nil
public internal(set) var revisionsPushedInLastSynch = [OCKEntity]()

var conflictPolicy = OCKMergeConflictResolutionPolicy.keepRemote

func pullRevisions(
since knowledgeVector: OCKRevisionRecord.KnowledgeVector,
mergeRevision: @escaping (OCKRevisionRecord, @escaping (Error?) -> Void) -> Void,
completion: @escaping (Error?) -> Void) {

timesPullWasCalled += 1
DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + delay) {
if !self.shouldSucceed {
completion(OCKStoreError.remoteSynchronizationFailed(reason: "Failed on purpose"))
return
}

let revision: OCKRevisionRecord!
if self.dummyKnowledgeVector == nil{
revision = OCKRevisionRecord(entities: [], knowledgeVector: .init())
}else{
revision = OCKRevisionRecord(entities: [], knowledgeVector: self.dummyKnowledgeVector!)
}

mergeRevision(revision, completion)
}
}

func pushRevisions(
deviceRevision: OCKRevisionRecord,
overwriteRemote: Bool,
completion: @escaping (Error?) -> Void) {

timesPushWasCalled += 1
timesForcePushed += overwriteRemote ? 1 : 0

//Save latest revisions
revisionsPushedInLastSynch.removeAll()
revisionsPushedInLastSynch.append(contentsOf: deviceRevision.entities)

//Update KnowledgeVector
if dummyKnowledgeVector == nil{
dummyKnowledgeVector = .init([uuid:0])
}
dummyKnowledgeVector?.increment(clockFor: uuid)
dummyKnowledgeVector?.merge(with: deviceRevision.knowledgeVector)

completion(nil)
}

func chooseConflictResolutionPolicy(
_ conflict: OCKMergeConflictDescription,
completion: @escaping (OCKMergeConflictResolutionPolicy) -> Void) {
completion(conflictPolicy)
}

func dummyRevision() -> OCKRevisionRecord {
let schedule = OCKSchedule.mealTimesEachDay(start: Date(), end: nil)
var task = OCKTask(id: "a", title: "A", carePlanUUID: nil, schedule: schedule)
task.uuid = UUID()
task.createdDate = Date()
task.updatedDate = task.createdDate

var outcome = OCKOutcome(taskUUID: task.uuid!, taskOccurrenceIndex: 0, values: [])
outcome.uuid = UUID()
outcome.createdDate = Date()
outcome.updatedDate = outcome.createdDate

let entities: [OCKEntity] = [
.task(task),
.outcome(outcome)
]

let revision: OCKRevisionRecord!
if self.dummyKnowledgeVector == nil{
revision = OCKRevisionRecord(entities: entities, knowledgeVector: .init())
}else{
revision = OCKRevisionRecord(entities: entities, knowledgeVector: self.dummyKnowledgeVector!)
}

return revision
}
}
13 changes: 13 additions & 0 deletions CareKitStore/CareKitStoreTests/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,16 @@ extension OCKObjectCompatible {
return uuid
}
}

extension OCKVersionedObjectCompatible {
func getPreviousVersionUUID() throws -> UUID {
guard let uuid = previousVersionUUID else { throw OCKStoreError.invalidValue(reason: "Missing previousVersionUUID") }
return uuid
}

func getNextVersionUUID() throws -> UUID {
guard let uuid = nextVersionUUID else { throw OCKStoreError.invalidValue(reason: "Missing nextVersionUUID") }
return uuid
}
}

22 changes: 22 additions & 0 deletions CareKitUI.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Pod::Spec.new do |s|
s.name = 'CareKitUI'
s.version = '2.0'
s.summary = 'CareKit is an open source software framework for creating apps that help people better understand and manage their health.'
s.homepage = 'https://github.com/carekit-apple/CareKit/'
s.documentation_url = 'https://developer.apple.com/documentation/carekit'
s.screenshots = [ 'https://user-images.githubusercontent.com/51756298/69096972-66de0b00-0a0a-11ea-96f0-4605d04ab396.gif',
'https://user-images.githubusercontent.com/51756298/69107801-7586eb00-0a27-11ea-8aa2-eca687602c76.gif']
s.license = { :type => 'BSD', :file => 'LICENSE' }
s.author = { 'researchandcare.org' => 'https://www.researchandcare.org' }
s.platform = :ios
s.ios.deployment_target = '13.0'
#s.osx.deployment_target = '10.15'
s.swift_versions = '5.0'
s.source = { :git => 'https://github.com/carekit-apple/carekit.git', :tag => s.version.to_s}

s.source_files = 'CareKitUI/CareKitUI/**/*'
#s.resources = ['CareKitUI/CareKitUI/Supporting Files/Localization/*.lproj']
s.exclude_files = ['CareKitUI/CareKitUI/**/*.plist']
s.frameworks = 'UIKit', 'SwiftUI'

end