AbstractNetworkReportNodeTest.java

/**
 * Copyright (c) 2023, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.iidm.network.tck;

import com.powsybl.commons.report.PowsyblCoreReportResourceBundle;
import com.powsybl.commons.test.PowsyblCoreTestReportResourceBundle;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.ReportNodeContext;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.util.Networks;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
 */
public abstract class AbstractNetworkReportNodeTest {

    @Test
    public void executeWithReportNodeTest() {
        // Create a network and affect it a reportNode (reportNode1)
        Network network = EurostagTutorialExample1Factory.create();
        ReportNode reportNode1 = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("key1")
                .build();
        network.getReportNodeContext().pushReportNode(reportNode1);
        assertTrue(reportNode1.getChildren().isEmpty());

        // Create another reportNode (reportNode2)
        ReportNode reportNode2 = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("key2")
                .build();
        assertTrue(reportNode2.getChildren().isEmpty());

        // Execute a task using reportNode2
        Networks.executeWithReportNode(network, reportNode2, getTask(network));

        // The network's reportNode is still reportNode1
        assertEquals(reportNode1, network.getReportNodeContext().getReportNode());
        // reportNode1 wasn't affected
        assertTrue(reportNode1.getChildren().isEmpty());
        // reportNode2 was used by the task
        assertEquals(1, reportNode2.getChildren().size());
    }

    private Runnable getTask(Network network) {
        return () -> network.getReportNodeContext().getReportNode().newReportNode()
                .withMessageTemplate("reportKey")
                .add();
    }

    @Test
    public void multiThreadTest() throws InterruptedException {
        // Create a network and affect it a reportNode (reportNode1)
        Network network = EurostagTutorialExample1Factory.create();
        ReportNode reportNode1 = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("key1")
                .build();
        network.getReportNodeContext().pushReportNode(reportNode1);
        assertTrue(reportNode1.getChildren().isEmpty());

        // Create 2 other reportNodes (reportNode2 and reportNode3)
        ReportNode reportNode2 = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("key2")
                .build();
        ReportNode reportNode3 = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("key3")
                .build();
        assertTrue(reportNode2.getChildren().isEmpty());
        assertTrue(reportNode3.getChildren().isEmpty());

        // Switch in multi-thread management
        network.allowReportNodeContextMultiThreadAccess(true);

        final CountDownLatch latch = new CountDownLatch(2); // to sync threads after having set the working variant
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.invokeAll(Arrays.asList(
                () -> {
                    network.getReportNodeContext().pushReportNode(reportNode2);
                    try {
                        latch.countDown();
                        latch.await();
                        network.getReportNodeContext().getReportNode().newReportNode()
                                .withMessageTemplate("key2")
                                .add();
                    } finally {
                        network.getReportNodeContext().popReportNode();
                    }
                    return null;
                },
                () -> {
                    network.getReportNodeContext().pushReportNode(reportNode3);
                    try {
                        latch.countDown();
                        latch.await();
                        network.getReportNodeContext().getReportNode().newReportNode()
                                .withMessageTemplate("key3")
                                .add();
                    } finally {
                        network.getReportNodeContext().popReportNode();
                    }
                    return null;
                })
        );
        service.shutdown();
        boolean finished = service.awaitTermination(20, TimeUnit.SECONDS);
        if (!finished) {
            fail("Error executing test");
        }

        // Switch back to mono-thread management
        network.allowReportNodeContextMultiThreadAccess(false);

        // The network's reportNode is still reportNode1
        assertEquals(reportNode1, network.getReportNodeContext().getReportNode());
        // reportNode1 wasn't affected
        assertTrue(reportNode1.getChildren().isEmpty());
        // reportNode2 was used by the 1st task
        assertEquals(1, reportNode2.getChildren().size());
        assertEquals("key2", reportNode2.getChildren().stream().findFirst().orElseThrow().getMessageKey());
        // reportNode3 was used by the 2nd task
        assertEquals(1, reportNode3.getChildren().size());
        assertEquals("key3", reportNode3.getChildren().stream().findFirst().orElseThrow().getMessageKey());
    }

    @Test
    public void onSubnetworkTest() {
        // Create a network and affect it a reportNode (reportNode1)
        Network network = Network.create("Root", "format0");
        ReportNodeContext reportNodeContext1 = network.getReportNodeContext();

        // Create a subnetwork
        Network subnetwork = network.createSubnetwork("Sub1", "Sub1", "format1");

        // Check that the retrieved reportNode context is the same as the root network
        assertEquals(reportNodeContext1, subnetwork.getReportNodeContext());

        // Context change on the network/subnetwork is reflected to the other
        network.allowReportNodeContextMultiThreadAccess(true);
        ReportNodeContext reportNodeContext2 = network.getReportNodeContext();
        assertNotEquals(reportNodeContext1, reportNodeContext2);
        assertEquals(reportNodeContext2, subnetwork.getReportNodeContext());

        subnetwork.allowReportNodeContextMultiThreadAccess(false);
        ReportNodeContext reportNodeContext3 = subnetwork.getReportNodeContext();
        assertNotEquals(reportNodeContext2, reportNodeContext3);
        assertEquals(reportNodeContext3, network.getReportNodeContext());
    }

}