GlucoseMathTests.swift 13 KB

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