From 749bc49ad599f704fd94323243053ff890187f2d Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 17 Feb 2021 09:26:17 +0100 Subject: [PATCH] more CommandQueue tests --- .../plugins/pump/combo/ComboPlugin.java | 4 + .../plugins/pump/mdi/MDIPlugin.java | 25 ++- .../plugins/pump/virtual/VirtualPumpPlugin.kt | 1 + .../androidaps/queue/CommandQueue.kt | 24 ++- .../androidaps/queue/QueueThread.kt | 2 +- .../queue/commands/CommandLoadTDDs.kt | 2 +- .../queue/commands/CommandTempBasalPercent.kt | 2 +- .../nightscout/androidaps/TestPumpPlugin.kt | 64 ++++++ .../androidaps/queue/CommandQueueTest.kt | 199 ++++++++++++++++-- .../androidaps/queue/QueueThreadTest.kt | 98 +++++++++ .../androidaps/interfaces/PumpInterface.kt | 3 +- .../androidaps/queue/commands/Command.kt | 2 + .../omnipod/eros/OmnipodErosPumpPlugin.java | 28 +-- 13 files changed, 393 insertions(+), 61 deletions(-) create mode 100644 app/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt create mode 100644 app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java index eb80447c94..15ae4409ad 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java @@ -846,6 +846,10 @@ public class ComboPlugin extends PumpPluginBase implements PumpInterface, Constr } } + @Override public int waitForDisconnectionInSeconds() { + return 0; + } + private interface CommandExecution { CommandResult execute(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java index 3da7a4af01..143d60d606 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java @@ -21,7 +21,6 @@ import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.PumpPluginBase; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; @@ -44,7 +43,6 @@ public class MDIPlugin extends PumpPluginBase implements PumpInterface { public MDIPlugin( HasAndroidInjector injector, AAPSLogger aapsLogger, - RxBusWrapper rxBus, ResourceHelper resourceHelper, CommandQueueProvider commandQueue, TreatmentsPlugin treatmentsPlugin @@ -76,8 +74,7 @@ public class MDIPlugin extends PumpPluginBase implements PumpInterface { @NonNull @Override public PumpEnactResult loadTDDs() { //no result, could read DB in the future? - PumpEnactResult result = new PumpEnactResult(getInjector()); - return result; + return new PumpEnactResult(getInjector()); } @Override @@ -111,11 +108,15 @@ public class MDIPlugin extends PumpPluginBase implements PumpInterface { } @Override - public void connect(String reason) { + public void connect(@NonNull String reason) { } @Override - public void disconnect(String reason) { + public void disconnect(@NonNull String reason) { + } + + @Override public int waitForDisconnectionInSeconds() { + return 0; } @Override @@ -123,11 +124,11 @@ public class MDIPlugin extends PumpPluginBase implements PumpInterface { } @Override - public void getPumpStatus(String reason) { + public void getPumpStatus(@NonNull String reason) { } @NonNull @Override - public PumpEnactResult setNewBasalProfile(Profile profile) { + public PumpEnactResult setNewBasalProfile(@NonNull Profile profile) { // Do nothing here. we are using ConfigBuilderPlugin.getPlugin().getActiveProfile().getProfile(); PumpEnactResult result = new PumpEnactResult(getInjector()); result.success = true; @@ -135,7 +136,7 @@ public class MDIPlugin extends PumpPluginBase implements PumpInterface { } @Override - public boolean isThisProfileSet(Profile profile) { + public boolean isThisProfileSet(@NonNull Profile profile) { return false; } @@ -228,16 +229,14 @@ public class MDIPlugin extends PumpPluginBase implements PumpInterface { try { status.put("status", "normal"); extended.put("Version", version); - try { - extended.put("ActiveProfile", profileName); - } catch (Exception e) { - } + extended.put("ActiveProfile", profileName); status.put("timestamp", DateUtil.toISOString(now)); pump.put("status", status); pump.put("extended", extended); pump.put("clock", DateUtil.toISOString(now)); } catch (JSONException e) { + getAapsLogger().error("Exception: ", e); } return pump; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt index 2cd35dfca8..48a9bbc942 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt @@ -145,6 +145,7 @@ class VirtualPumpPlugin @Inject constructor( lastDataTime = System.currentTimeMillis() } + override fun waitForDisconnectionInSeconds(): Int = 0 override fun disconnect(reason: String) {} override fun stopConnecting() {} override fun getPumpStatus(reason: String) { diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt index 9cc9e1720d..20ffdf8425 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt @@ -84,7 +84,7 @@ import javax.inject.Singleton */ @Singleton -class CommandQueue @Inject constructor( +open class CommandQueue @Inject constructor( private val injector: HasAndroidInjector, private val aapsLogger: AAPSLogger, private val rxBus: RxBusWrapper, @@ -145,7 +145,7 @@ class CommandQueue @Inject constructor( @Suppress("SameParameterValue") @Synchronized - private fun isLastScheduled(type: CommandType): Boolean { + fun isLastScheduled(type: CommandType): Boolean { synchronized(queue) { if (queue.size > 0 && queue[queue.size - 1].commandType == type) { return true @@ -187,11 +187,8 @@ class CommandQueue @Inject constructor( // After new command added to the queue // start thread again if not already running @Synchronized - private fun notifyAboutNewCommand() { - while (thread != null && thread!!.state != Thread.State.TERMINATED && thread!!.waitingForDisconnect) { - aapsLogger.debug(LTag.PUMPQUEUE, "Waiting for previous thread finish") - SystemClock.sleep(500) - } + open fun notifyAboutNewCommand() { + waitForFinishedThread() if (thread == null || thread!!.state == Thread.State.TERMINATED) { thread = QueueThread(this, context, aapsLogger, rxBus, activePlugin.get(), resourceHelper, sp) thread!!.start() @@ -201,6 +198,15 @@ class CommandQueue @Inject constructor( } } + fun waitForFinishedThread() { + thread?.let { thread -> + while (thread.state != Thread.State.TERMINATED && thread.waitingForDisconnect) { + aapsLogger.debug(LTag.PUMPQUEUE, "Waiting for previous thread finish") + SystemClock.sleep(500) + } + } + } + override fun independentConnect(reason: String, callback: Callback?) { aapsLogger.debug(LTag.PUMPQUEUE, "Starting new queue") val tempCommandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, activePlugin, context, sp, buildHelper, fabricPrivacy) @@ -454,12 +460,12 @@ class CommandQueue @Inject constructor( // returns true if command is queued override fun loadTDDs(callback: Callback?): Boolean { - if (isRunning(CommandType.LOAD_HISTORY)) { + if (isRunning(CommandType.LOAD_TDD)) { callback?.result(executingNowError())?.run() return false } // remove all unfinished - removeAll(CommandType.LOAD_HISTORY) + removeAll(CommandType.LOAD_TDD) // add new command to queue add(CommandLoadTDDs(injector, callback)) notifyAboutNewCommand() diff --git a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt index bc2f4786e8..740f3f2839 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.kt @@ -127,7 +127,7 @@ class QueueThread internal constructor( } if (queue.size() == 0 && queue.performing() == null) { val secondsFromLastCommand = (System.currentTimeMillis() - lastCommandTime) / 1000 - if (secondsFromLastCommand >= 5) { + if (secondsFromLastCommand >= pump.waitForDisconnectionInSeconds()) { waitingForDisconnect = true aapsLogger.debug(LTag.PUMPQUEUE, "queue empty. disconnect") rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)) diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadTDDs.kt b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadTDDs.kt index 96f1834c35..9bf4a1ab7d 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadTDDs.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandLoadTDDs.kt @@ -9,7 +9,7 @@ import javax.inject.Inject class CommandLoadTDDs( injector: HasAndroidInjector, callback: Callback? -) : Command(injector, CommandType.LOAD_HISTORY, callback) { +) : Command(injector, CommandType.LOAD_TDD, callback) { @Inject lateinit var activePlugin: ActivePluginProvider diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.kt b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.kt index 2c5fd55041..8bfdbb73b7 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandTempBasalPercent.kt @@ -14,7 +14,7 @@ class CommandTempBasalPercent( private val enforceNew: Boolean, private val profile: Profile, callback: Callback? -) : Command(injector, CommandType.BASAL_PROFILE, callback) { +) : Command(injector, CommandType.TEMPBASAL, callback) { @Inject lateinit var activePlugin: ActivePluginProvider diff --git a/app/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt b/app/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt new file mode 100644 index 0000000000..936fb4345b --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/TestPumpPlugin.kt @@ -0,0 +1,64 @@ +package info.nightscout.androidaps + +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.data.DetailedBolusInfo +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.data.PumpEnactResult +import info.nightscout.androidaps.interfaces.PumpDescription +import info.nightscout.androidaps.interfaces.PumpInterface +import info.nightscout.androidaps.plugins.common.ManufacturerType +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType +import info.nightscout.androidaps.utils.TimeChangeType +import org.json.JSONObject + +@Suppress("MemberVisibilityCanBePrivate") +class TestPumpPlugin(val injector: HasAndroidInjector) : PumpInterface { + + override var isConnected = false + override var isConnecting = false + override var isHandshakeInProgress = false + val lastData = 0L + + val baseBasal = 0.0 + override val pumpDescription = PumpDescription() + + override val isInitialized: Boolean = true + override val isSuspended: Boolean = false + override val isBusy: Boolean = false + override fun connect(reason: String) { + isConnected = true + } + + override fun disconnect(reason: String) { + isConnected = false + } + + override fun stopConnecting() { + isConnected = false + } + + override fun waitForDisconnectionInSeconds(): Int = 0 + override fun getPumpStatus(reason: String) {} + override fun setNewBasalProfile(profile: Profile): PumpEnactResult = PumpEnactResult(injector) + override fun isThisProfileSet(profile: Profile): Boolean = true + override fun lastDataTime(): Long = lastData + override val baseBasalRate: Double = baseBasal + override val reservoirLevel: Double = 0.0 + override val batteryLevel: Int = 0 + override fun deliverTreatment(detailedBolusInfo: DetailedBolusInfo): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun stopBolusDelivering() {} + override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun cancelExtendedBolus(): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun getJSONStatus(profile: Profile, profileName: String, version: String): JSONObject = JSONObject() + override fun manufacturer(): ManufacturerType = ManufacturerType.AndroidAPS + override fun model(): PumpType = PumpType.GenericAAPS + override fun serialNumber(): String = "1" + override fun shortStatus(veryShort: Boolean): String = "" + override val isFakingTempsByExtendedBoluses: Boolean = false + override fun loadTDDs(): PumpEnactResult = PumpEnactResult(injector).success(true) + override fun canHandleDST(): Boolean = true + override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) {} +} \ No newline at end of file diff --git a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt index da805527b5..3518805b82 100644 --- a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt @@ -7,20 +7,24 @@ import dagger.android.AndroidInjector import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Config import info.nightscout.androidaps.TestBaseWithProfile +import info.nightscout.androidaps.TestPumpPlugin import info.nightscout.androidaps.data.DetailedBolusInfo import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.Constraint -import info.nightscout.androidaps.interfaces.PumpDescription +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin -import info.nightscout.androidaps.queue.commands.Command -import info.nightscout.androidaps.queue.commands.CustomCommand +import info.nightscout.androidaps.queue.commands.* import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.rx.AapsSchedulers import info.nightscout.androidaps.utils.sharedPreferences.SP import org.junit.Assert import org.junit.Before @@ -42,34 +46,67 @@ class CommandQueueTest : TestBaseWithProfile() { @Mock lateinit var lazyActivePlugin: Lazy @Mock lateinit var activePlugin: ActivePluginProvider @Mock lateinit var context: Context - @Mock lateinit var virtualPumpPlugin: VirtualPumpPlugin @Mock lateinit var sp: SP @Mock lateinit var loggerUtils: LoggerUtils @Mock lateinit var powerManager: PowerManager + class CommandQueueMocked( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + rxBus: RxBusWrapper, + aapsSchedulers: AapsSchedulers, + resourceHelper: ResourceHelper, + constraintChecker: ConstraintChecker, + profileFunction: ProfileFunction, + activePlugin: Lazy, + context: Context, + sp: SP, + buildHelper: BuildHelper, + fabricPrivacy: FabricPrivacy + ) : CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, activePlugin, context, sp, buildHelper, fabricPrivacy) { + + override fun notifyAboutNewCommand() {} + + } + val injector = HasAndroidInjector { AndroidInjector { if (it is Command) { it.aapsLogger = aapsLogger it.resourceHelper = resourceHelper } + if (it is CommandTempBasalPercent) { + it.activePlugin = activePlugin + } + if (it is CommandBolus) { + it.activePlugin = activePlugin + it.rxBus = rxBus + } + if (it is CommandCustomCommand) { + it.activePlugin = activePlugin + } + if (it is CommandExtendedBolus) { + it.activePlugin = activePlugin + } + if (it is CommandLoadHistory) { + it.activePlugin = activePlugin + } } } lateinit var commandQueue: CommandQueue + lateinit var testPumpPlugin: TestPumpPlugin @Before fun prepare() { - commandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, BuildHelper(Config(), loggerUtils), fabricPrivacy) + commandQueue = CommandQueueMocked(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, BuildHelper(Config(), loggerUtils), fabricPrivacy) + testPumpPlugin = TestPumpPlugin(injector) - val pumpDescription = PumpDescription() - pumpDescription.basalMinimumRate = 0.1 + testPumpPlugin.pumpDescription.basalMinimumRate = 0.1 `when`(context.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager) `when`(lazyActivePlugin.get()).thenReturn(activePlugin) - `when`(activePlugin.activePump).thenReturn(virtualPumpPlugin) - `when`(virtualPumpPlugin.pumpDescription).thenReturn(pumpDescription) - `when`(virtualPumpPlugin.isThisProfileSet(anyObject())).thenReturn(false) + `when`(activePlugin.activePump).thenReturn(testPumpPlugin) `when`(activePlugin.activeTreatments).thenReturn(treatmentsPlugin) `when`(treatmentsPlugin.lastBolusTime).thenReturn(Calendar.getInstance().also { it.set(2000, 0, 1) }.timeInMillis) `when`(profileFunction.getProfile()).thenReturn(validProfile) @@ -85,6 +122,22 @@ class CommandQueueTest : TestBaseWithProfile() { `when`(constraintChecker.applyBasalPercentConstraints(anyObject(), anyObject())).thenReturn(percentageConstraint) } + @Test + fun commandIsPickedUp() { + val commandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, BuildHelper(Config(), loggerUtils), fabricPrivacy) + // start with empty queue + Assert.assertEquals(0, commandQueue.size()) + + // add bolus command + commandQueue.bolus(DetailedBolusInfo(), null) + Assert.assertEquals(1, commandQueue.size()) + + commandQueue.waitForFinishedThread() + Thread.sleep(1000) + + Assert.assertEquals(0, commandQueue.size()) + } + @Test fun doTests() { @@ -115,16 +168,20 @@ class CommandQueueTest : TestBaseWithProfile() { commandQueue.tempBasalPercent(0, 30, true, validProfile, null) Assert.assertEquals(1, commandQueue.size()) + // cancel tempbasal it should replace previous TEMPBASAL + commandQueue.cancelTempBasal(false, null) + Assert.assertEquals(1, commandQueue.size()) + // add extended bolus commandQueue.extendedBolus(1.0, 30, null) Assert.assertEquals(2, commandQueue.size()) - // add cancel temp basal should remove previous 2 temp basal setting + // add extended should remove previous extended setting commandQueue.extendedBolus(1.0, 30, null) Assert.assertEquals(2, commandQueue.size()) // cancel extended bolus should replace previous extended - commandQueue.extendedBolus(1.0, 30, null) + commandQueue.cancelExtended(null) Assert.assertEquals(2, commandQueue.size()) // add setProfile @@ -225,21 +282,105 @@ class CommandQueueTest : TestBaseWithProfile() { } @Test - fun isCustomCommandInQueue() { + fun isSetUserOptionsCommandInQueue() { // given Assert.assertEquals(0, commandQueue.size()) // when - val queued1 = commandQueue.customCommand(CustomCommand1(), null) - val queued2 = commandQueue.customCommand(CustomCommand2(), null) + commandQueue.setUserOptions(null) // then - Assert.assertTrue(queued1) - Assert.assertTrue(queued2) - Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand1::class.java)) - Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand2::class.java)) - Assert.assertFalse(commandQueue.isCustomCommandInQueue(CustomCommand3::class.java)) - Assert.assertEquals(2, commandQueue.size()) + Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.SET_USER_SETTINGS)) + Assert.assertEquals(1, commandQueue.size()) + // next should be ignored + commandQueue.setUserOptions(null) + Assert.assertEquals(1, commandQueue.size()) + } + + @Test + fun isLoadEventsCommandInQueue() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + commandQueue.loadEvents(null) + + // then + Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.LOAD_EVENTS)) + Assert.assertEquals(1, commandQueue.size()) + // next should be ignored + commandQueue.loadEvents(null) + Assert.assertEquals(1, commandQueue.size()) + } + + @Test + fun isLoadTDDsCommandInQueue() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + commandQueue.loadTDDs(null) + + // then + Assert.assertEquals(1, commandQueue.size()) + // next should be ignored + commandQueue.loadTDDs(null) + Assert.assertEquals(1, commandQueue.size()) + } + + @Test + fun isLoadHistoryCommandInQueue() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + commandQueue.loadHistory(0, null) + + // then + Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.LOAD_HISTORY)) + Assert.assertEquals(1, commandQueue.size()) + // next should be ignored + commandQueue.loadHistory(0, null) + Assert.assertEquals(1, commandQueue.size()) + } + + @Test + fun isStopCommandInQueue() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + commandQueue.stopPump(null) + + // then + Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.STOP_PUMP)) + Assert.assertEquals(1, commandQueue.size()) + } + + @Test + fun isStarCommandInQueue() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + commandQueue.startPump(null) + + // then + Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.START_PUMP)) + Assert.assertEquals(1, commandQueue.size()) + } + + @Test + fun isSetTbrNotificationCommandInQueue() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + commandQueue.setTBROverNotification(null, true) + + // then + Assert.assertTrue(commandQueue.isLastScheduled(Command.CommandType.INSIGHT_SET_TBR_OVER_ALARM)) + Assert.assertEquals(1, commandQueue.size()) } @Test @@ -272,6 +413,22 @@ class CommandQueueTest : TestBaseWithProfile() { Assert.assertEquals(1, commandQueue.size()) } + @Test + fun readStatusTwiceIsNotAllowed() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + val queued1 = commandQueue.readStatus("1", null) + val queued2 = commandQueue.readStatus("2", null) + + // then + Assert.assertTrue(queued1) + Assert.assertFalse(queued2) + Assert.assertEquals(1, commandQueue.size()) + Assert.assertTrue(commandQueue.statusInQueue()) + } + private class CustomCommand1 : CustomCommand { override val statusDescription: String diff --git a/app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt b/app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt new file mode 100644 index 0000000000..55e7aa55a5 --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/queue/QueueThreadTest.kt @@ -0,0 +1,98 @@ +package info.nightscout.androidaps.queue + +import android.content.Context +import android.os.PowerManager +import dagger.Lazy +import dagger.android.AndroidInjector +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Config +import info.nightscout.androidaps.TestBaseWithProfile +import info.nightscout.androidaps.TestPumpPlugin +import info.nightscout.androidaps.interfaces.ActivePluginProvider +import info.nightscout.androidaps.interfaces.Constraint +import info.nightscout.androidaps.interfaces.PumpDescription +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils +import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.commands.Command +import info.nightscout.androidaps.queue.commands.CommandTempBasalAbsolute +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner +import java.util.* + +@RunWith(PowerMockRunner::class) +@PrepareForTest( + ConstraintChecker::class, VirtualPumpPlugin::class, ToastUtils::class, Context::class, + TreatmentsPlugin::class, FabricPrivacy::class, LoggerUtils::class, PowerManager::class) +class QueueThreadTest : TestBaseWithProfile() { + + @Mock lateinit var constraintChecker: ConstraintChecker + @Mock lateinit var lazyActivePlugin: Lazy + @Mock lateinit var activePlugin: ActivePluginProvider + @Mock lateinit var context: Context + @Mock lateinit var sp: SP + @Mock lateinit var loggerUtils: LoggerUtils + @Mock lateinit var powerManager: PowerManager + + val injector = HasAndroidInjector { + AndroidInjector { + if (it is Command) { + it.aapsLogger = aapsLogger + it.resourceHelper = resourceHelper + } + if (it is CommandTempBasalAbsolute) { + it.activePlugin = activePlugin + } + } + } + + private lateinit var pumpPlugin: TestPumpPlugin + lateinit var commandQueue: CommandQueue + lateinit var sut: QueueThread + + @Before + fun prepare() { + pumpPlugin = TestPumpPlugin(injector) + commandQueue = CommandQueue(injector, aapsLogger, rxBus, aapsSchedulers, resourceHelper, constraintChecker, profileFunction, lazyActivePlugin, context, sp, BuildHelper(Config(), loggerUtils), fabricPrivacy) + + val pumpDescription = PumpDescription() + pumpDescription.basalMinimumRate = 0.1 + + Mockito.`when`(context.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager) + Mockito.`when`(lazyActivePlugin.get()).thenReturn(activePlugin) + Mockito.`when`(activePlugin.activePump).thenReturn(pumpPlugin) + Mockito.`when`(activePlugin.activeTreatments).thenReturn(treatmentsPlugin) + Mockito.`when`(treatmentsPlugin.lastBolusTime).thenReturn(Calendar.getInstance().also { it.set(2000, 0, 1) }.timeInMillis) + Mockito.`when`(profileFunction.getProfile()).thenReturn(validProfile) + + val bolusConstraint = Constraint(0.0) + Mockito.`when`(constraintChecker.applyBolusConstraints(anyObject())).thenReturn(bolusConstraint) + Mockito.`when`(constraintChecker.applyExtendedBolusConstraints(anyObject())).thenReturn(bolusConstraint) + val carbsConstraint = Constraint(0) + Mockito.`when`(constraintChecker.applyCarbsConstraints(anyObject())).thenReturn(carbsConstraint) + val rateConstraint = Constraint(0.0) + Mockito.`when`(constraintChecker.applyBasalConstraints(anyObject(), anyObject())).thenReturn(rateConstraint) + val percentageConstraint = Constraint(0) + Mockito.`when`(constraintChecker.applyBasalPercentConstraints(anyObject(), anyObject())).thenReturn(percentageConstraint) + + sut = QueueThread(commandQueue, context, aapsLogger, rxBus, activePlugin, resourceHelper, sp) + } + + @Test + fun commandIsPickedUp() { + commandQueue.tempBasalAbsolute(2.0, 60, true, validProfile, null) + sut.run() + Assert.assertEquals(0, commandQueue.size()) + } +} \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.kt index a8d1e886d7..cac4d2e29d 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.kt @@ -25,6 +25,7 @@ interface PumpInterface { @JvmDefault fun finishHandshaking() {} // set initial handshake completed fun connect(reason: String) fun disconnect(reason: String) + @JvmDefault fun waitForDisconnectionInSeconds(): Int = 5 // wait [x] second after last command before sending disconnect fun stopConnecting() fun getPumpStatus(reason: String) @@ -65,7 +66,7 @@ interface PumpInterface { /** * Provides a list of custom actions to be displayed in the Actions tab. - * Plese note that these actions will not be queued upon execution + * Please note that these actions will not be queued upon execution * * @return list of custom actions */ diff --git a/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt b/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt index 149810ea35..1b90b24da4 100644 --- a/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt +++ b/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt @@ -28,6 +28,7 @@ abstract class Command( READSTATUS, LOAD_HISTORY, // TDDs and so far only Dana specific LOAD_EVENTS, // so far only Dana specific + LOAD_TDD, SET_USER_SETTINGS, // so far only Dana specific, START_PUMP, STOP_PUMP, @@ -36,6 +37,7 @@ abstract class Command( } init { + @Suppress("LeakingThis") injector.androidInjector().inject(this) } diff --git a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/OmnipodErosPumpPlugin.java b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/OmnipodErosPumpPlugin.java index 43b5ba32e3..f66a7552b5 100644 --- a/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/OmnipodErosPumpPlugin.java +++ b/omnipod-eros/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/eros/OmnipodErosPumpPlugin.java @@ -88,7 +88,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.eros.queue.command.Comman import info.nightscout.androidaps.plugins.pump.omnipod.eros.queue.command.CommandUpdateAlertConfiguration; import info.nightscout.androidaps.plugins.pump.omnipod.eros.queue.command.OmnipodCustomCommand; import info.nightscout.androidaps.plugins.pump.omnipod.eros.queue.command.OmnipodCustomCommandType; -import info.nightscout.androidaps.plugins.pump.omnipod.eros.rileylink.manager.OmnipodRileyLinkCommunicationManager; import info.nightscout.androidaps.plugins.pump.omnipod.eros.rileylink.service.RileyLinkOmnipodService; import info.nightscout.androidaps.plugins.pump.omnipod.eros.ui.OmnipodOverviewFragment; import info.nightscout.androidaps.plugins.pump.omnipod.eros.util.AapsOmnipodUtil; @@ -137,7 +136,6 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa private final DateUtil dateUtil; private final PumpDescription pumpDescription; private final ServiceConnection serviceConnection; - private final OmnipodRileyLinkCommunicationManager omnipodRileyLinkCommunicationManager; private final PumpType pumpType = PumpType.Insulet_Omnipod; private final CompositeDisposable disposables = new CompositeDisposable(); private final NSUpload nsUpload; @@ -175,8 +173,7 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa RileyLinkUtil rileyLinkUtil, OmnipodAlertUtil omnipodAlertUtil, ProfileFunction profileFunction, - NSUpload nsUpload, - OmnipodRileyLinkCommunicationManager omnipodRileyLinkCommunicationManager + NSUpload nsUpload ) { super(new PluginDescription() // .mainType(PluginType.PUMP) // @@ -204,7 +201,6 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa this.omnipodAlertUtil = omnipodAlertUtil; this.profileFunction = profileFunction; this.nsUpload = nsUpload; - this.omnipodRileyLinkCommunicationManager = omnipodRileyLinkCommunicationManager; pumpDescription = new PumpDescription(pumpType); @@ -560,7 +556,7 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa * When the user explicitly requested it by clicking the Refresh button on the Omnipod tab (which is executed through {@link #executeCustomCommand(CustomCommand)}) */ @Override - public void getPumpStatus(String reason) { + public void getPumpStatus(@NonNull String reason) { if (firstRun) { initializeAfterRileyLinkConnection(); firstRun = false; @@ -581,7 +577,7 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa @NonNull @Override - public PumpEnactResult setNewBasalProfile(Profile profile) { + public PumpEnactResult setNewBasalProfile(@NonNull Profile profile) { PumpEnactResult result = executeCommand(OmnipodCommandType.SET_BASAL_PROFILE, () -> aapsOmnipodManager.setBasalProfile(profile, true)); aapsLogger.info(LTag.PUMP, "Basal Profile was set: " + result.success); @@ -590,7 +586,7 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa } @Override - public boolean isThisProfileSet(Profile profile) { + public boolean isThisProfileSet(@NonNull Profile profile) { if (!podStateManager.isPodActivationCompleted()) { // When no Pod is active, return true here in order to prevent AAPS from setting a profile // When we activate a new Pod, we just use ProfileFunction to set the currently active profile @@ -661,7 +657,7 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa // if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed @Override @NonNull - public PumpEnactResult setTempBasalAbsolute(double absoluteRate, int durationInMinutes, Profile profile, boolean enforceNew) { + public PumpEnactResult setTempBasalAbsolute(double absoluteRate, int durationInMinutes, @NonNull Profile profile, boolean enforceNew) { aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute: rate: {}, duration={}", absoluteRate, durationInMinutes); if (durationInMinutes <= 0 || durationInMinutes % BASAL_STEP_DURATION.getStandardMinutes() != 0) { @@ -709,7 +705,7 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa // TODO improve (i8n and more) @NonNull @Override - public JSONObject getJSONStatus(Profile profile, String profileName, String version) { + public JSONObject getJSONStatus(@NonNull Profile profile, @NonNull String profileName, @NonNull String version) { if (!podStateManager.isPodActivationCompleted() || lastConnectionTimeMillis + 60 * 60 * 1000L < System.currentTimeMillis()) { return new JSONObject(); @@ -821,12 +817,12 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa } @Override - public void executeCustomAction(CustomActionType customActionType) { + public void executeCustomAction(@NonNull CustomActionType customActionType) { aapsLogger.warn(LTag.PUMP, "Unknown custom action: " + customActionType); } @Override - public PumpEnactResult executeCustomCommand(CustomCommand command) { + public PumpEnactResult executeCustomCommand(@NonNull CustomCommand command) { if (!(command instanceof OmnipodCustomCommand)) { aapsLogger.warn(LTag.PUMP, "Unknown custom command: " + command.getClass().getName()); return new PumpEnactResult(getInjector()).success(false).enacted(false).comment(resourceHelper.gs(R.string.omnipod_error_unknown_custom_command, command.getClass().getName())); @@ -1010,12 +1006,16 @@ public class OmnipodErosPumpPlugin extends PumpPluginBase implements PumpInterfa aapsLogger.debug(LTag.PUMP, "finishHandshaking [OmnipodPumpPlugin] - default (empty) implementation."); } - @Override public void connect(String reason) { + @Override public void connect(@NonNull String reason) { if (displayConnectionMessages) aapsLogger.debug(LTag.PUMP, "connect (reason={}) [PumpPluginAbstract] - default (empty) implementation." + reason); } - @Override public void disconnect(String reason) { + @Override public int waitForDisconnectionInSeconds() { + return 0; + } + + @Override public void disconnect(@NonNull String reason) { if (displayConnectionMessages) aapsLogger.debug(LTag.PUMP, "disconnect (reason={}) [PumpPluginAbstract] - default (empty) implementation." + reason); }