diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java b/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java index 1d0610fb..634b3edf 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/BlockDeviceProvider.java @@ -55,7 +55,7 @@ public interface BlockDeviceProvider extends IForgeRegistryEntry - * This is the equivalent of {@link RPCDevice#unmount()} or {@link VMDevice#unmount()}, + * This is the equivalent of {@link RPCDevice#dispose()} or {@link VMDevice#dispose()}, * for devices that have gone missing unexpectedly, so this method could no longer be * called on the actual device. *

diff --git a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java index 3088a8a0..4a5e1086 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java +++ b/src/main/java/li/cil/oc2/api/bus/device/provider/ItemDeviceProvider.java @@ -65,7 +65,7 @@ public interface ItemDeviceProvider extends IForgeRegistryEntry - * This is the equivalent of {@link RPCDevice#unmount()} or {@link VMDevice#unmount()}, + * This is the equivalent of {@link RPCDevice#dispose()} or {@link VMDevice#dispose()}, * for devices that have gone missing unexpectedly, so this method could no longer be * called on the actual device. *

diff --git a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java index f35f45d4..615c2739 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/rpc/RPCDevice.java @@ -21,25 +21,25 @@ import java.util.List; * The lifecycle for {@link RPCDevice}s is as follows: *

  * ┌──────────────┐ ┌────────────────┐
- * │serializeNBT()│ │deserializeNBT()◄─────────────────┐
- * └──────────────┘ └───────┬────────┘                 │
- *   May be called          │VM starts or              │
- *   at any time,           │resumes after             │
- *   except while           │load                      │
- *   unloaded...        ┌───▼───┐                      │
- *                      │mount()│                      │
- *                      └───┬───┘                      │
- *                          │VM stops or               │
- *                          │is unloaded               │
- *                          │                          │
- *                     ┌────▼────┐Chunk unloaded       │
- *                     │unmount()├─────────────────────┤
- *                     └────┬────┘                     │
- *                          │VM stopped or             │
- *                          │device removed            │
- *                          │                          │
- *                     ┌────▼────┐                     │
- *                     │dispose()├─────────────────────┘
+ * │serializeNBT()│ │deserializeNBT()◄───────┐
+ * └──────────────┘ └───────┬────────┘       │
+ *   May be called          │VM starts or    │
+ *   at any time,    ┌──────┤resumes after   │
+ *   except while    │      │load            │
+ *   unloaded...     │  ┌───▼───┐            │
+ *                   │  │mount()│            │
+ *                   │  └───┬───┘            │Chunk
+ *                   │      │VM stops or     │unloaded
+ *                   │      │is unloaded     │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   │ │unmount()├───────────┤
+ *                   │ └────┬────┘           │
+ *                   │      │VM stopped or   │
+ *                   │      │device removed  │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   └─┤dispose()├───────────┘
  *                     └─────────┘
  * 
* diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java index 7de911f6..526924fb 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMDevice.java @@ -19,53 +19,30 @@ import li.cil.sedna.api.device.MemoryMappedDevice; *

* The lifecycle for VMDevices can be depicted as such: *

- *    ┌──────────────────────────────────┐
- *    │VirtualMachine.isRunning() = false◄────────────────────────┐
- *    └────────────────┬─────────────────┘                        │
- *                     │                                          │
- *          ┌──────────▼───────────┐                              │
- *          │VirtualMachine.start()│                              │
- *          └──────────┬───────────┘                              │
- *                     │                                          │
- *                     │   ┌──────────┐                           │
- *                     │   │Chunk Load│  ┌──────────────────┐     │
- *                     ├───┼──────────◄──┤VMDevice.suspend()◄───┐ │
- *                     │   │World Load│  └──────────────────┘   │ │
- *                     │   └──────────┘                         │ │
- *                     │                                        │ │
- *            ┌────────▼───────┐       ┌────┐                   │ │
- * ┌──────────►VMDevice.mount()◄───────┤Wait◄─────────┐         │ │
- * │          └────────┬───────┘       └──▲─┘         │         │ │
- * │                   │                  │           │         │ │
- * │                   │  ┌───────────────┴─────────┐ │         │ │
- * │                   ├──►VMDeviceLoadResult.fail()│ │         │ │
- * │                   │  └─────────────────────────┘ │         │ │
- * │                   │                              │         │ │
- * │    ┌──────────────▼─────────────┐ ┌──────────────┴───┐     │ │
- * │    │VMDeviceLoadResult.success()│ │VMDevice.unmount()│     │ │
- * │    └──────────────┬─────────────┘ └──────────────▲───┘     │ │
- * │                   │                              │         │ │
- * │                   │        ┌─────────────────────┴───┐     │ │
- * │                   ├────────►     Other VMDevice:     │     │ │
- * │                   │        │VMDeviceLoadResult.fail()│     │ │
- * │                   │        └─────────────────────────┘     │ │
- * │                   │                                        │ │
- * │                   │                     ┌────────────┐     │ │
- * │                   │                     │Chunk Unload│     │ │
- * │                   │                   ┌─►────────────┼─────┘ │
- * │                   │                   │ │World Unload│       │
- * │ ┌─────────────────▼───────────────┐   │ └────────────┘       │
- * │ │VirtualMachine.isRunning() = true├───┤                      │
- * │ └─────┬───────────────────┬───────┘   │ ┌──────────────────┐ │
- * │       │                   │           │ │Computer Shutdown │ │
- * │ ┌─────▼──────┐     ┌──────▼───────┐   └─►──────────────────┤ │
- * └─┤Device Added│     │Device Removed│     │Computer Destroyed│ │
- *   └────────────┘     └──────┬───────┘     └─────────┬────────┘ │
- *                             │                       │          │
- *                    ┌────────▼─────────┐   ┌─────────▼────────┐ │
- *                    │VMDevice.unmount()│   │VMDevice.unmount()├─┘
- *                    └──────────────────┘   └──────────────────┘
+ * ┌──────────────┐ ┌────────────────┐
+ * │serializeNBT()│ │deserializeNBT()◄───────┐
+ * └──────────────┘ └───────┬────────┘       │
+ *   May be called          │VM starts or    │
+ *   at any time,    ┌──────┤resumes after   │
+ *   except while    │      │load            │
+ *   unloaded...     │  ┌───▼───┐            │
+ *                   │  │mount()│            │
+ *                   │  └───┬───┘            │Chunk
+ *                   │      │VM stops or     │unloaded
+ *                   │      │is unloaded     │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   │ │unmount()├───────────┤
+ *                   │ └────┬────┘           │
+ *                   │      │VM stopped or   │
+ *                   │      │device removed  │
+ *                   │      │                │
+ *                   │ ┌────▼────┐           │
+ *                   └─┤dispose()├───────────┘
+ *                     └─────────┘
  * 
+ * Note that if any other {@link VMDevice} fails mounting, all mounted devices + * will immediately unmounted and disposed. * * @see li.cil.oc2.api.bus.device.provider.BlockDeviceProvider * @see li.cil.oc2.api.bus.device.provider.ItemDeviceProvider @@ -106,5 +83,5 @@ public interface VMDevice extends Device { * Intended for soft-releasing resources acquired in {@link #mount(VMContext)}, i.e. non-persisted * unmanaged resources. */ - void suspend(); + void dispose(); } diff --git a/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java index 2b537f88..d799e7a0 100644 --- a/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java +++ b/src/main/java/li/cil/oc2/common/bus/RPCDeviceBusAdapter.java @@ -74,6 +74,7 @@ public final class RPCDeviceBusAdapter implements Steppable { for (final RPCDevice device : unmountedDevices) { device.mount(); } + mountedDevices.addAll(unmountedDevices); unmountedDevices.clear(); } @@ -82,18 +83,15 @@ public final class RPCDeviceBusAdapter implements Steppable { for (final RPCDevice device : mountedDevices) { device.unmount(); } + + unmountedDevices.addAll(mountedDevices); + mountedDevices.clear(); } public void dispose() { - for (final RPCDevice device : mountedDevices) { - device.unmount(); - device.dispose(); - } - for (final RPCDeviceList device : unmountedDevices) { - device.dispose(); - } - unmountedDevices.addAll(mountedDevices); - mountedDevices.clear(); + unmount(); + + unmountedDevices.forEach(RPCDeviceList::dispose); } public void reset() { diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java index 0eded280..2f44b96c 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/item/AbstractBlockDeviceVMDevice.java @@ -81,14 +81,6 @@ public abstract class AbstractBlockDeviceVMDevice @Override public void unmount() { - suspend(); + memoryMap = null; } @Override - public void suspend() { - memoryMap = null; + public void dispose() { } @Subscribe diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java b/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java index 84cd5c69..34c8a8d9 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java +++ b/src/main/java/li/cil/oc2/common/bus/device/item/HardDriveVMDeviceWithInitialData.java @@ -67,8 +67,8 @@ public final class HardDriveVMDeviceWithInitialData extends HardDriveVMDevice { @Override protected void closeBlockDevice() { // Join the copy job before releasing the device to avoid writes from thread to closed device. - // Since we use memory mapped memory, closing the device leads to it holding a dead pointer, meaning - // further access to it will hard-crash the JVM. + // Since we use memory mapped memory, closing the device leads to it holding a dead pointer, + // meaning further access to it will hard-crash the JVM. joinCopyJob(); super.closeBlockDevice(); diff --git a/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java b/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java index 72658858..9717f8ae 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/item/MemoryDevice.java @@ -63,32 +63,30 @@ public final class MemoryDevice extends IdentityProxy implements VMDe @Override public void unmount() { - suspend(); - - // Memory is volatile, so free up our persisted blob when device is unloaded. - if (blobHandle != null) { - BlobStorage.delete(blobHandle); - blobHandle = null; - } - - address.clear(); - } - - @Override - public void suspend() { if (device != null) { try { device.close(); } catch (final Exception e) { LOGGER.error(e); } + + device = null; } if (blobHandle != null) { BlobStorage.close(blobHandle); } + } - device = null; + @Override + public void dispose() { + // Memory is volatile, so free up our persisted blob when device is disposed. + if (blobHandle != null) { + BlobStorage.delete(blobHandle); + blobHandle = null; + } + + address.clear(); } @Override diff --git a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java index 0e37586c..dc503e61 100644 --- a/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java +++ b/src/main/java/li/cil/oc2/common/vm/AbstractVirtualMachine.java @@ -90,7 +90,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine { public void suspend() { joinWorkerThread(); - state.vmAdapter.suspend(); + state.vmAdapter.unmount(); state.rpcAdapter.unmount(); } @@ -204,7 +204,7 @@ public abstract class AbstractVirtualMachine implements VirtualMachine { state.board.reset(); state.rpcAdapter.reset(); state.rpcAdapter.dispose(); - state.vmAdapter.unmount(); + state.vmAdapter.dispose(); runner = null; } diff --git a/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java b/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java index b4ccb572..ca455e0e 100644 --- a/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java +++ b/src/main/java/li/cil/oc2/common/vm/VMDeviceBusAdapter.java @@ -12,8 +12,8 @@ import java.util.HashMap; import java.util.OptionalLong; public final class VMDeviceBusAdapter { - private final HashMap deviceContexts = new HashMap<>(); - private final ArrayList incompleteLoads = new ArrayList<>(); + private final HashMap mountedDevices = new HashMap<>(); + private final ArrayList unmountedDevices = new ArrayList<>(); private BaseAddressProvider baseAddressProvider = unused -> OptionalLong.empty(); /////////////////////////////////////////////////////////////////// @@ -33,28 +33,30 @@ public final class VMDeviceBusAdapter { } public VMDeviceLoadResult mount() { - for (int i = 0; i < incompleteLoads.size(); i++) { - final VMDevice device = incompleteLoads.get(i); + globalContext.joinWorkerThread(); + for (final VMDevice device : unmountedDevices) { final ManagedVMContext context = new ManagedVMContext(globalContext, globalContext, () -> baseAddressProvider.getBaseAddress(device)); - deviceContexts.put(device, context); - final VMDeviceLoadResult result = device.mount(context); context.freeze(); if (!result.wasSuccessful()) { - for (; i >= 0; i--) { - final VMDevice otherDevice = incompleteLoads.get(i); - otherDevice.unmount(); - deviceContexts.get(otherDevice).invalidate(); - } + context.invalidate(); + mountedDevices.forEach((mountedDevice, mountedContext) -> { + mountedDevice.unmount(); + mountedDevice.dispose(); + mountedContext.invalidate(); + }); + mountedDevices.clear(); return result; } + + mountedDevices.put(device, context); } - incompleteLoads.clear(); + unmountedDevices.clear(); globalContext.updateReservations(); @@ -64,21 +66,19 @@ public final class VMDeviceBusAdapter { public void unmount() { globalContext.joinWorkerThread(); - for (final VMDevice device : deviceContexts.keySet()) { + mountedDevices.forEach((device, context) -> { device.unmount(); - } + context.invalidate(); + }); - unload(); + unmountedDevices.addAll(mountedDevices.keySet()); + mountedDevices.clear(); } - public void suspend() { - globalContext.joinWorkerThread(); + public void dispose() { + unmount(); - for (final VMDevice device : deviceContexts.keySet()) { - device.suspend(); - } - - unload(); + unmountedDevices.forEach(VMDevice::dispose); } public void addDevices(final Collection devices) { @@ -86,12 +86,11 @@ public final class VMDeviceBusAdapter { for (final Device device : devices) { if (device instanceof final VMDevice vmDevice) { - final ManagedVMContext context = deviceContexts.put(vmDevice, null); - if (context != null) { - context.invalidate(); + // Add to set of unmounted devices if we don't already track it. It's a set, so + // there won't be duplicates in the unmounted set due to this. + if (!mountedDevices.containsKey(vmDevice)) { + unmountedDevices.add(vmDevice); } - - incompleteLoads.add(vmDevice); } } } @@ -101,28 +100,16 @@ public final class VMDeviceBusAdapter { for (final Device device : devices) { if (device instanceof final VMDevice vmDevice) { - vmDevice.unmount(); - - final ManagedVMContext context = deviceContexts.remove(vmDevice); + final ManagedVMContext context = mountedDevices.remove(vmDevice); if (context != null) { + vmDevice.unmount(); + vmDevice.dispose(); context.invalidate(); + } else { + unmountedDevices.remove(vmDevice); + vmDevice.dispose(); } - - incompleteLoads.remove(vmDevice); } } } - - /////////////////////////////////////////////////////////////////// - - private void unload() { - deviceContexts.forEach((device, context) -> { - if (context != null) { - context.invalidate(); - } - }); - - incompleteLoads.clear(); - incompleteLoads.addAll(deviceContexts.keySet()); - } } diff --git a/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java b/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java index c3be390d..41dcb916 100644 --- a/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java +++ b/src/test/java/li/cil/oc2/common/bus/RPCDeviceTests.java @@ -120,6 +120,17 @@ public final class RPCDeviceTests { verify(device).dispose(); } + @Test + public void devicesHaveMountCalledAfterGlobalUnmount() { + final RPCDevice device = addDevice(); + adapter.resume(controller, true); + adapter.mount(); + adapter.unmount(); + + adapter.mount(); + verify(device, times(2)).mount(); + } + @Test public void deviceListIsStable() { final RPCDevice device1 = mock(RPCDevice.class); diff --git a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java index ab9b1ebf..b0c01a68 100644 --- a/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java +++ b/src/test/java/li/cil/oc2/common/bus/VMDeviceTests.java @@ -1,8 +1,8 @@ package li.cil.oc2.common.bus; -import li.cil.oc2.api.bus.device.vm.context.VMContext; import li.cil.oc2.api.bus.device.vm.VMDevice; import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult; +import li.cil.oc2.api.bus.device.vm.context.VMContext; import li.cil.oc2.common.vm.VMDeviceBusAdapter; import li.cil.oc2.common.vm.context.global.GlobalVMContext; import li.cil.sedna.api.Board; @@ -59,25 +59,72 @@ public final class VMDeviceTests { adapter = new VMDeviceBusAdapter(context); } + @Test + public void addingDeviceDoesNotMountDirectly() { + final VMDevice device = mock(VMDevice.class); + when(device.mount(any())).thenReturn(VMDeviceLoadResult.success()); + + adapter.addDevices(Collections.singleton(device)); + verify(device, never()).mount(any()); + } + @Test public void addedDevicesHaveMountCalled() { + final VMDevice device = mock(VMDevice.class); + when(device.mount(any())).thenReturn(VMDeviceLoadResult.success()); + + adapter.addDevices(Collections.singleton(device)); + assertTrue(adapter.mount().wasSuccessful()); + verify(device).mount(any()); + } + + @Test + public void existingDevicesDoNotHaveMountCalledAgain() { final VMDevice device1 = mock(VMDevice.class); final VMDevice device2 = mock(VMDevice.class); when(device1.mount(any())).thenReturn(VMDeviceLoadResult.success()); when(device2.mount(any())).thenReturn(VMDeviceLoadResult.success()); adapter.addDevices(Collections.singleton(device1)); - assertTrue(adapter.mount().wasSuccessful()); + adapter.mount(); verify(device1).mount(any()); adapter.addDevices(Collections.singleton(device2)); - assertTrue(adapter.mount().wasSuccessful()); + adapter.mount(); verifyNoMoreInteractions(device1); - verify(device2).mount(any()); } @Test - public void removedDevicesHaveUnmountCalled() { + public void deviceFailingMountDoesNotHaveUnmountOrDisposeCalled() { + final VMDevice device = mock(VMDevice.class); + when(device.mount(any())).thenReturn(VMDeviceLoadResult.fail()); + + adapter.addDevices(Collections.singleton(device)); + assertFalse(adapter.mount().wasSuccessful()); + verify(device).mount(any()); + verify(device, never()).unmount(); + verify(device, never()).dispose(); + } + + @Test + public void mountedDevicesAreUnmountedAndDisposedIfOtherMountFails() { + final VMDevice device1 = mock(VMDevice.class); + final VMDevice device2 = mock(VMDevice.class); + when(device1.mount(any())).thenReturn(VMDeviceLoadResult.success()); + when(device2.mount(any())).thenReturn(VMDeviceLoadResult.fail()); + + adapter.addDevices(Collections.singleton(device1)); + adapter.addDevices(Collections.singleton(device2)); + adapter.mount(); + + verify(device1).mount(any()); + verify(device2).mount(any()); + verify(device1).unmount(); + verify(device1).dispose(); + } + + @Test + public void mountedDevicesAreUnmountedAndDisposedWhenRemoved() { final VMDevice device = mock(VMDevice.class); when(device.mount(any())).thenReturn(VMDeviceLoadResult.success()); @@ -86,10 +133,21 @@ public final class VMDeviceTests { adapter.removeDevices(Collections.singleton(device)); verify(device).unmount(); + verify(device).dispose(); } @Test - public void devicesHaveUnloadCalledOnGlobalUnmount() { + public void unmountedDevicesAreDisposedWhenRemoved() { + final VMDevice device = mock(VMDevice.class); + + adapter.addDevices(Collections.singleton(device)); + + adapter.removeDevices(Collections.singleton(device)); + verify(device).dispose(); + } + + @Test + public void mountedDevicesAreUnmountedButNotDisposedOnGlobalUnmount() { final VMDevice device = mock(VMDevice.class); when(device.mount(any())).thenReturn(VMDeviceLoadResult.success()); @@ -98,6 +156,40 @@ public final class VMDeviceTests { adapter.unmount(); verify(device).unmount(); + verify(device, never()).dispose(); + } + + @Test + public void unmountedDevicesAreNotUnmountedAndNotDisposedOnGlobalUnmount() { + final VMDevice device = mock(VMDevice.class); + + adapter.addDevices(Collections.singleton(device)); + + adapter.unmount(); + verify(device, never()).unmount(); + verify(device, never()).dispose(); + } + + @Test + public void mountedDevicesAreUnmountedAndDisposedOnGlobalDispose() { + final VMDevice device = mock(VMDevice.class); + when(device.mount(any())).thenReturn(VMDeviceLoadResult.success()); + adapter.addDevices(Collections.singleton(device)); + adapter.mount(); + + adapter.dispose(); + verify(device).unmount(); + verify(device).dispose(); + } + + @Test + public void unmountedDevicesAreNotUnmountedButDisposedOnGlobalDispose() { + final VMDevice device = mock(VMDevice.class); + adapter.addDevices(Collections.singleton(device)); + + adapter.dispose(); + verify(device, never()).unmount(); + verify(device).dispose(); } @Test @@ -106,11 +198,8 @@ public final class VMDeviceTests { when(device.mount(any())).thenReturn(VMDeviceLoadResult.success()); adapter.addDevices(Collections.singleton(device)); - assertTrue(adapter.mount().wasSuccessful()); - verify(device).mount(any()); - + adapter.mount(); adapter.unmount(); - verify(device).unmount(); assertTrue(adapter.mount().wasSuccessful()); verify(device, times(2)).mount(any()); @@ -188,7 +277,7 @@ public final class VMDeviceTests { final int someInterruptMask = 0x1; assertThrows(IllegalArgumentException.class, () -> - deviceData.context.getInterruptController().raiseInterrupts(someInterruptMask)); + deviceData.context.getInterruptController().raiseInterrupts(someInterruptMask)); } @Test @@ -226,7 +315,7 @@ public final class VMDeviceTests { final VMContext context = invocation.getArgument(0); assertThrows(UnsupportedOperationException.class, () -> - context.getMemoryMap().addDevice(0, mock(MemoryMappedDevice.class))); + context.getMemoryMap().addDevice(0, mock(MemoryMappedDevice.class))); return VMDeviceLoadResult.success(); });