MealJsonTests.swift 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. @Suite("Meal testing using JSON inputs", .serialized) struct MealJsonTests {
  5. let timeZoneForTests = TimeZoneForTests()
  6. @Test(
  7. "Meal should produce same results for fixed JS",
  8. .enabled(if: ReplayTests.enabled)
  9. ) func replayErrorInputs() async throws {
  10. // Note: This test case can only test one timezone per invocation
  11. // so you need to manually change this to try out errors from
  12. // different timezones
  13. let testingTimezone = ReplayTests.timezone
  14. let files = try await HttpFiles.listFiles()
  15. for filePath in files {
  16. let algorithmComparison = try await HttpFiles.downloadFile(at: filePath)
  17. print("Checking \(filePath) @ \(algorithmComparison.createdAt)")
  18. guard algorithmComparison.timezone == testingTimezone else {
  19. continue
  20. }
  21. guard let mealInputs = algorithmComparison.mealInput else {
  22. print("Skipping, no mealInputs found")
  23. if let str = algorithmComparison.comparisonError {
  24. print(str)
  25. }
  26. if let str = algorithmComparison.swiftException {
  27. print(str)
  28. #expect(Bool(false), "Swift exception on meal")
  29. }
  30. continue
  31. }
  32. // The JS implementation of IoB when the pump is suspend is so fundamentally
  33. // broken that I wasn't able to fix it in JS. So we'll just skip these, but I
  34. // verified them by hand and the Swift implementation appears to be correct
  35. if let mostRecentPumpEvent = mealInputs.pumpHistory.filter({ $0.isExternal != true }).first {
  36. if mostRecentPumpEvent.type == .pumpSuspend
  37. {
  38. print("Skipping, known issue with JS and currently suspended pumps")
  39. continue
  40. }
  41. }
  42. timeZoneForTests.setTimezone(identifier: algorithmComparison.timezone)
  43. try await checkFixedJsAgainstSwift(mealInputs: mealInputs)
  44. print("Checked \(filePath) \(algorithmComparison.timezone)")
  45. timeZoneForTests.resetTimezone()
  46. }
  47. }
  48. func checkFixedJsAgainstSwift(mealInputs: MealInputs) async throws {
  49. let openAps = OpenAPSFixed()
  50. let (mealResultSwift, _) = OpenAPSSwift.meal(
  51. pumphistory: mealInputs.pumpHistory,
  52. profile: try JSONBridge.to(mealInputs.profile),
  53. basalProfile: mealInputs.basalProfile,
  54. clock: mealInputs.clock,
  55. carbs: mealInputs.carbs,
  56. glucose: mealInputs.glucose
  57. )
  58. let mealResultJavascript = await openAps.mealJavascript(
  59. pumphistory: mealInputs.pumpHistory,
  60. profile: try JSONBridge.to(mealInputs.profile),
  61. basalProfile: mealInputs.basalProfile,
  62. clock: mealInputs.clock,
  63. carbs: mealInputs.carbs,
  64. glucose: mealInputs.glucose
  65. )
  66. let comparison = JSONCompare.createComparison(
  67. function: .meal,
  68. swift: mealResultSwift,
  69. swiftDuration: 0.1,
  70. javascript: mealResultJavascript,
  71. javascriptDuration: 0.1,
  72. iobInputs: nil,
  73. mealInputs: nil,
  74. autosensInputs: nil,
  75. determineBasalInputs: nil
  76. )
  77. if comparison.resultType == .valueDifference {
  78. print(comparison.differences!.prettyPrintedJSON!)
  79. }
  80. if comparison.resultType != .matching {
  81. print("REPLAY ERROR: Fixed JS didn't match")
  82. }
  83. #expect(comparison.resultType == .matching)
  84. }
  85. @Test("Format meal inputs for running in JS", .enabled(if: false)) func formatInputs() async throws {
  86. let openAps = OpenAPSFixed()
  87. // this test is meant for one-off analysis so it's ok to hard code
  88. // a file, just make sure to _not_ check in updates to this to
  89. // avoid polluting our change logs
  90. let algorithmComparison = try await HttpFiles.downloadFile(at: "/files/7a8a377e-f483-46a5-adbb-290baa04801b.3.json")
  91. let mealInputs = algorithmComparison.mealInput!
  92. let encoder = JSONCoding.encoder
  93. let output = try encoder.encode(mealInputs)
  94. let sharedDir = FileManager.default.temporaryDirectory
  95. let outputURL = sharedDir.appendingPathComponent("meal_error_inputs.json")
  96. // Print the path so you can find it
  97. print("Writing to: \(outputURL.path)")
  98. try output.write(to: outputURL)
  99. timeZoneForTests.setTimezone(identifier: algorithmComparison.timezone)
  100. let (mealResultSwift, _) = OpenAPSSwift.meal(
  101. pumphistory: mealInputs.pumpHistory,
  102. profile: try JSONBridge.to(mealInputs.profile),
  103. basalProfile: mealInputs.basalProfile,
  104. clock: mealInputs.clock,
  105. carbs: mealInputs.carbs,
  106. glucose: mealInputs.glucose
  107. )
  108. print("Swift result")
  109. switch mealResultSwift {
  110. case let .success(rawJson):
  111. print(rawJson)
  112. case let .failure(error):
  113. print(error.localizedDescription)
  114. }
  115. let mealResultJavascript = await openAps.mealJavascript(
  116. pumphistory: mealInputs.pumpHistory,
  117. profile: try JSONBridge.to(mealInputs.profile),
  118. basalProfile: mealInputs.basalProfile,
  119. clock: mealInputs.clock,
  120. carbs: mealInputs.carbs,
  121. glucose: mealInputs.glucose
  122. )
  123. print("Fixed JS result")
  124. switch mealResultJavascript {
  125. case let .success(rawJson):
  126. print(rawJson)
  127. case let .failure(error):
  128. print(error.localizedDescription)
  129. }
  130. timeZoneForTests.resetTimezone()
  131. }
  132. }