GlucoseMathTests.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. //
  2. // GlucoseMathTests.swift
  3. // GlucoseKitTests
  4. //
  5. // Created by Nathan Racklyeft on 1/24/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import XCTest
  9. @testable import LoopKit
  10. import HealthKit
  11. public struct GlucoseFixtureValue: GlucoseSampleValue {
  12. public let startDate: Date
  13. public let quantity: HKQuantity
  14. public let isDisplayOnly: Bool
  15. public let provenanceIdentifier: String
  16. public init(startDate: Date, quantity: HKQuantity, isDisplayOnly: Bool, provenanceIdentifier: String?) {
  17. self.startDate = startDate
  18. self.quantity = quantity
  19. self.isDisplayOnly = isDisplayOnly
  20. self.provenanceIdentifier = provenanceIdentifier ?? "com.loopkit.LoopKitTests"
  21. }
  22. }
  23. extension GlucoseFixtureValue: Comparable {
  24. public static func <(lhs: GlucoseFixtureValue, rhs: GlucoseFixtureValue) -> Bool {
  25. return lhs.startDate < rhs.startDate
  26. }
  27. public static func ==(lhs: GlucoseFixtureValue, rhs: GlucoseFixtureValue) -> Bool {
  28. return lhs.startDate == rhs.startDate &&
  29. lhs.quantity == rhs.quantity &&
  30. lhs.isDisplayOnly == rhs.isDisplayOnly &&
  31. lhs.provenanceIdentifier == rhs.provenanceIdentifier
  32. }
  33. }
  34. class GlucoseMathTests: XCTestCase {
  35. private func printFixture(_ effectVelocity: [GlucoseEffectVelocity]) {
  36. let formatter = ISO8601DateFormatter.localTimeDate()
  37. let unit = HKUnit.milligramsPerDeciliter.unitDivided(by: .minute())
  38. print("\n\n")
  39. print(String(data: try! JSONSerialization.data(
  40. withJSONObject: effectVelocity.map({ (value) -> [String: Any] in
  41. return [
  42. "startDate": formatter.string(from: value.startDate),
  43. "endDate": formatter.string(from: value.endDate),
  44. "value": value.quantity.doubleValue(for: unit),
  45. "unit": unit.unitString
  46. ]
  47. }),
  48. options: .prettyPrinted), encoding: .utf8)!)
  49. print("\n\n")
  50. }
  51. func loadInputFixture(_ resourceName: String) -> [GlucoseFixtureValue] {
  52. let fixture: [JSONDictionary] = loadFixture(resourceName)
  53. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  54. return fixture.map {
  55. return GlucoseFixtureValue(
  56. startDate: dateFormatter.date(from: $0["date"] as! String)!,
  57. quantity: HKQuantity(unit: HKUnit.milligramsPerDeciliter, doubleValue: $0["amount"] as! Double),
  58. isDisplayOnly: ($0["display_only"] as? Bool) ?? false,
  59. provenanceIdentifier: $0["provenance_identifier"] as? String
  60. )
  61. }
  62. }
  63. func loadOutputFixture(_ resourceName: String) -> [GlucoseEffect] {
  64. let fixture: [JSONDictionary] = loadFixture(resourceName)
  65. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  66. return fixture.map {
  67. return GlucoseEffect(startDate: dateFormatter.date(from: $0["date"] as! String)!, quantity: HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue: $0["amount"] as! Double))
  68. }
  69. }
  70. func loadEffectVelocityFixture(_ resourceName: String) -> [GlucoseEffectVelocity] {
  71. let fixture: [JSONDictionary] = loadFixture(resourceName)
  72. let dateFormatter = ISO8601DateFormatter.localTimeDate()
  73. return fixture.map {
  74. return GlucoseEffectVelocity(startDate: dateFormatter.date(from: $0["startDate"] as! String)!, endDate: dateFormatter.date(from: $0["endDate"] as! String)!, quantity: HKQuantity(unit: HKUnit(from: $0["unit"] as! String), doubleValue:$0["value"] as! Double))
  75. }
  76. }
  77. func testMomentumEffectForBouncingGlucose() {
  78. let input = loadInputFixture("momentum_effect_bouncing_glucose_input")
  79. let output = loadOutputFixture("momentum_effect_bouncing_glucose_output")
  80. let effects = input.linearMomentumEffect()
  81. let unit = HKUnit.milligramsPerDeciliter
  82. XCTAssertEqual(output.count, effects.count)
  83. for (expected, calculated) in zip(output, effects) {
  84. XCTAssertEqual(expected.startDate, calculated.startDate)
  85. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  86. }
  87. }
  88. func testMomentumEffectForRisingGlucose() {
  89. let input = loadInputFixture("momentum_effect_rising_glucose_input")
  90. let output = loadOutputFixture("momentum_effect_rising_glucose_output")
  91. let effects = input.linearMomentumEffect()
  92. let unit = HKUnit.milligramsPerDeciliter
  93. XCTAssertEqual(output.count, effects.count)
  94. for (expected, calculated) in zip(output, effects) {
  95. XCTAssertEqual(expected.startDate, calculated.startDate)
  96. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  97. }
  98. }
  99. func testMomentumEffectForRisingGlucoseDoubles() {
  100. let input = loadInputFixture("momentum_effect_rising_glucose_double_entries_input")
  101. let output = loadOutputFixture("momentum_effect_rising_glucose_output")
  102. let effects = input.linearMomentumEffect()
  103. let unit = HKUnit.milligramsPerDeciliter
  104. XCTAssertEqual(output.count, effects.count)
  105. for (expected, calculated) in zip(output, effects) {
  106. XCTAssertEqual(expected.startDate, calculated.startDate)
  107. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  108. }
  109. }
  110. func testMomentumEffectForFallingGlucose() {
  111. let input = loadInputFixture("momentum_effect_falling_glucose_input")
  112. let output = loadOutputFixture("momentum_effect_falling_glucose_output")
  113. let effects = input.linearMomentumEffect()
  114. let unit = HKUnit.milligramsPerDeciliter
  115. XCTAssertEqual(output.count, effects.count)
  116. for (expected, calculated) in zip(output, effects) {
  117. XCTAssertEqual(expected.startDate, calculated.startDate)
  118. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  119. }
  120. }
  121. func testMomentumEffectForFallingGlucoseDuplicates() {
  122. var input = loadInputFixture("momentum_effect_falling_glucose_input")
  123. let output = loadOutputFixture("momentum_effect_falling_glucose_output")
  124. input.append(contentsOf: input)
  125. input.sort(by: <)
  126. let effects = input.linearMomentumEffect()
  127. let unit = HKUnit.milligramsPerDeciliter
  128. XCTAssertEqual(output.count, effects.count)
  129. for (expected, calculated) in zip(output, effects) {
  130. XCTAssertEqual(expected.startDate, calculated.startDate)
  131. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  132. }
  133. }
  134. func testMomentumEffectForStableGlucose() {
  135. let input = loadInputFixture("momentum_effect_stable_glucose_input")
  136. let output = loadOutputFixture("momentum_effect_stable_glucose_output")
  137. let effects = input.linearMomentumEffect()
  138. let unit = HKUnit.milligramsPerDeciliter
  139. XCTAssertEqual(output.count, effects.count)
  140. for (expected, calculated) in zip(output, effects) {
  141. XCTAssertEqual(expected.startDate, calculated.startDate)
  142. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  143. }
  144. }
  145. func testMomentumEffectForDuplicateGlucose() {
  146. let input = loadInputFixture("momentum_effect_duplicate_glucose_input")
  147. let effects = input.linearMomentumEffect()
  148. XCTAssertEqual(0, effects.count)
  149. }
  150. func testMomentumEffectForEmptyGlucose() {
  151. let input = [GlucoseFixtureValue]()
  152. let effects = input.linearMomentumEffect()
  153. XCTAssertEqual(0, effects.count)
  154. }
  155. func testMomentumEffectForSpacedOutGlucose() {
  156. let input = loadInputFixture("momentum_effect_incomplete_glucose_input")
  157. let effects = input.linearMomentumEffect()
  158. XCTAssertEqual(0, effects.count)
  159. }
  160. func testMomentumEffectForTooFewGlucose() {
  161. let input = loadInputFixture("momentum_effect_bouncing_glucose_input")[0...1]
  162. let effects = input.linearMomentumEffect()
  163. XCTAssertEqual(0, effects.count)
  164. }
  165. func testMomentumEffectForDisplayOnlyGlucose() {
  166. let input = loadInputFixture("momentum_effect_display_only_glucose_input")
  167. let effects = input.linearMomentumEffect()
  168. XCTAssertEqual(0, effects.count)
  169. }
  170. func testMomentumEffectForMixedProvenanceGlucose() {
  171. let input = loadInputFixture("momentum_effect_mixed_provenance_glucose_input")
  172. let effects = input.linearMomentumEffect()
  173. XCTAssertEqual(0, effects.count)
  174. }
  175. func testCounteractionEffectsForFallingGlucose() {
  176. let input = loadInputFixture("counteraction_effect_falling_glucose_input")
  177. let insulinEffect = loadOutputFixture("counteraction_effect_falling_glucose_insulin")
  178. let output = loadEffectVelocityFixture("counteraction_effect_falling_glucose_output")
  179. let effects = input.counteractionEffects(to: insulinEffect)
  180. let unit = HKUnit.milligramsPerDeciliter.unitDivided(by: .minute())
  181. XCTAssertEqual(output.count, effects.count)
  182. for (expected, calculated) in zip(output, effects) {
  183. XCTAssertEqual(expected.startDate, calculated.startDate)
  184. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  185. }
  186. }
  187. func testCounteractionEffectsForFallingGlucoseDuplicates() {
  188. var input = loadInputFixture("counteraction_effect_falling_glucose_input")
  189. input.append(contentsOf: input)
  190. input.sort(by: <)
  191. let insulinEffect = loadOutputFixture("counteraction_effect_falling_glucose_insulin")
  192. let output = loadEffectVelocityFixture("counteraction_effect_falling_glucose_output")
  193. let effects = input.counteractionEffects(to: insulinEffect)
  194. let unit = HKUnit.milligramsPerDeciliter.unitDivided(by: .minute())
  195. XCTAssertEqual(output.count, effects.count)
  196. for (expected, calculated) in zip(output, effects) {
  197. XCTAssertEqual(expected.startDate, calculated.startDate)
  198. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  199. }
  200. }
  201. func testCounteractionEffectsForFallingGlucoseAlmostDuplicates() {
  202. let input = loadInputFixture("counteraction_effect_falling_glucose_almost_duplicates_input")
  203. let insulinEffect = loadOutputFixture("counteraction_effect_falling_glucose_insulin")
  204. let output = loadEffectVelocityFixture("counteraction_effect_falling_glucose_almost_duplicates_output")
  205. let effects = input.counteractionEffects(to: insulinEffect)
  206. let unit = HKUnit.milligramsPerDeciliter.unitDivided(by: .minute())
  207. XCTAssertEqual(output.count, effects.count)
  208. for (expected, calculated) in zip(output, effects) {
  209. XCTAssertEqual(expected.startDate, calculated.startDate)
  210. XCTAssertEqual(expected.endDate, calculated.endDate)
  211. XCTAssertEqual(expected.quantity.doubleValue(for: unit), calculated.quantity.doubleValue(for: unit), accuracy: Double(Float.ulpOfOne))
  212. }
  213. }
  214. func testCounteractionEffectsForNoGlucose() {
  215. let input = [GlucoseFixtureValue]()
  216. let insulinEffect = loadOutputFixture("counteraction_effect_falling_glucose_insulin")
  217. let output = [GlucoseEffectVelocity]()
  218. let effects = input.counteractionEffects(to: insulinEffect)
  219. XCTAssertEqual(output.count, effects.count)
  220. }
  221. }