@Test
public void submoduleOutPortHasSignal() {

    try (
        ModuleInterpretation moduleInterpretation = testCaseBuilder("submoduleOutPortHasSignal", PROXY_CONTEXT)
            .build()
    ) {
        MapStagingArea stagingArea = moduleInterpretation.getStagingArea();
        Map<ExecutionTrace, ObjectNode> stagingAreaMap = stagingArea.toUnmodifiableMap();
        put(stagingArea, ":in:x", ":in:y");
        Props props = moduleInterpretation.toCompositeModuleInterpreterProps(3, bitSetOf(), bitSetOf(0, 1));
        ActorSystem actorSystem = moduleInterpretation.getActorSystem();
        TestActorRef<CompositeModuleInterpreterActor> actorRef = TestActorRef.create(
            moduleInterpretation.getActorSystem(), props, moduleInterpretation.getSupervisor());
        TestProbe akkaEventProbe = new TestProbe(actorSystem);
        actorSystem.eventStream().subscribe(akkaEventProbe.ref(), Object.class);
        // Empty the executor queue, and subsequently verify that the appropriate actions happened as a result of
        // the message and starting the interpretation.
        CallingThreadExecutor asyncTaskExecutor = moduleInterpretation.getAsyncTaskExecutor();
        asyncTaskExecutor.executeAll();
        Assert.assertEquals(
            stagingAreaMap.keySet(),
            tracesSet(":in:x", ":in:y", "/a:in:ax", "/a:in:ay", "/b:in:bx", ":out:q")
        );
        TestProbe aProbe = moduleInterpretation.getTestProbeForChild("a");
        aProbe.expectMsgClass(InPortHasSignal.class);
        aProbe.expectMsgClass(InPortHasSignal.class);
        Assert.assertFalse(aProbe.msgAvailable());
        int aId = submodule(moduleInterpretation, "a").getIndex();
        put(stagingArea, "/a:out:ap");
        actorRef.receive(new SubmoduleOutPortHasSignal(aId, 0));
        // Verify warning if the SubmoduleOutPortHasSignal message mentions a port that already provided a value
        Assert.assertFalse(akkaEventProbe.msgAvailable());
        actorRef.receive(new SubmoduleOutPortHasSignal(aId, 0));
        LogEvent redundantMsgLogEvent = akkaEventProbe.expectMsgClass(LogEvent.class);
        Assert.assertEquals(redundantMsgLogEvent.logSource(), actorRef.path().toString());
        String redundantMsg = (String) redundantMsgLogEvent.message();
        Assert.assertTrue(redundantMsg.contains("redundant"));
        RuntimeParentModule parentModule = (RuntimeParentModule) moduleInterpretation.getResolvedModule();
        RuntimeModule submodule = Objects.requireNonNull(
            parentModule.getEnclosedElement(RuntimeModule.class, SimpleName.identifier("a")));
        Assert.assertTrue(redundantMsg.contains(submodule.getOutPorts().get(0).toString()));
        // Verify warning if the SubmoduleOutPortHasSignal message mentions a port that is not needed
        Assert.assertFalse(akkaEventProbe.msgAvailable());
        actorRef.receive(new SubmoduleOutPortHasSignal(aId, 1));
        LogEvent unexpectedMsgLogEvent = akkaEventProbe.expectMsgClass(LogEvent.class);
        Assert.assertEquals(unexpectedMsgLogEvent.logSource(), actorRef.path().toString());
        String unexpectedMsg = (String) unexpectedMsgLogEvent.message();
        Assert.assertTrue(unexpectedMsg.contains("unexpected"));
        Assert.assertTrue(unexpectedMsg.contains(submodule.getOutPorts().get(1).toString()));
        // Verify that all successors receive values as events are processed. Note that the value of out-port a#ap
        // will no longer be needed, so submodule a will have no needed out-ports any more and cleaning will happen.
        asyncTaskExecutor.executeAll();
        Assert.assertEquals(
            stagingAreaMap.keySet(),
            tracesSet(":in:x", ":in:y", "/b:in:bx", "/b:in:by", ":out:q")
        );
    }
}