AlternativeQueriesTester.java

/**
 * Copyright (c) 2017-2018, 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.cgmes.alternatives.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.powsybl.cgmes.model.CgmesModel;
import com.powsybl.cgmes.model.CgmesModelFactory;
import com.powsybl.cgmes.model.GridModelReference;
import com.powsybl.cgmes.model.triplestore.CgmesModelTripleStore;
import com.powsybl.commons.datasource.ReadOnlyDataSource;
import com.powsybl.triplestore.api.PropertyBags;
import com.powsybl.triplestore.api.QueryCatalog;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 */
class AlternativeQueriesTester {

    AlternativeQueriesTester(List<String> tripleStoreImplementations,
        QueryCatalog queries,
        GridModelReference gridModel,
        Expected expected) {
        // By default, execute the tests without caching the models
        this(tripleStoreImplementations, queries, gridModel, expected, 1, true, null, false);
    }

    AlternativeQueriesTester(List<String> tripleStoreImplementations,
        QueryCatalog queries,
        GridModelReference gridModel,
        Expected expected,
        boolean cacheModels) {
        this(tripleStoreImplementations, queries, gridModel, expected, 1, true, null, cacheModels);
    }

    AlternativeQueriesTester(List<String> tripleStoreImplementations,
        QueryCatalog queries,
        GridModelReference gridModel,
        Expected expected, int experiments,
        boolean doAssert,
        Consumer<PropertyBags> consumer,
        boolean cacheModels) {
        this.implementations = tripleStoreImplementations;
        this.queries = queries;
        this.gridModel = gridModel;
        this.expected = expected;
        this.experiments = experiments;
        this.doAssert = doAssert;
        this.consumer = consumer;
        this.cacheModels = cacheModels;
        this.cachedModels = cacheModels ? new HashMap<>(implementations.size()) : null;
    }

    Expected expected() {
        return this.expected;
    }

    void load() {
        if (cacheModels) {
            // Load the model for every triple store implementation
            for (String impl : implementations) {
                ReadOnlyDataSource dataSource = gridModel.dataSource();
                CgmesModel cgmes = CgmesModelFactory.create(dataSource, impl);
                assertTrue(cgmes instanceof CgmesModelTripleStore);
                cachedModels.put(impl, (CgmesModelTripleStore) cgmes);
            }
        }
    }

    void test(String alternative, Expected expected, Consumer<PropertyBags> consumer) throws IOException {
        String queryText = queries.get(alternative);
        assertNotNull(queryText);
        assertFalse(queryText.isEmpty());
        for (String impl : implementations) {
            testWithExperiments(alternative, impl, queryText, expected, consumer);
        }
    }

    void test(String alternative) throws IOException {
        // If no explicit expected result, use the default expected result for the
        // tester
        test(alternative, this.expected);
    }

    void test(String alternative, Expected expected) throws IOException {
        test(alternative, expected, this.consumer);
    }

    static class Expected {
        Expected() {
            resultSize = 0;
            propertyCount = new HashMap<>();
        }

        Expected resultSize(long resultSize) {
            this.resultSize = resultSize;
            return this;
        }

        long resultSize() {
            return this.resultSize;
        }

        Expected propertyCount(String property, long count) {
            this.propertyCount.put(property, count);
            return this;
        }

        private long resultSize;
        private final Map<String, Long> propertyCount;
    }

    private void testWithExperiments(String alternative,
        String impl,
        String queryText,
        Expected expected,
        Consumer<PropertyBags> consumer) throws IOException {

        CgmesModelTripleStore model = modelFor(impl);

        long dt0 = 0;
        if (experiments > 1) {
            // Initial run to compare against potential "caching" considerations
            // All engines have the opportunity to "activate" caching mechanisms
            final long t00 = System.currentTimeMillis();
            model.query(queryText);
            final long t10 = System.currentTimeMillis();
            dt0 = t10 - t00;
        }

        long dt = 0;
        long[] dts = new long[experiments];
        for (int k = 0; k < experiments; k++) {

            final long t0 = System.currentTimeMillis();
            PropertyBags result = model.query(queryText);
            final long t1 = System.currentTimeMillis();
            dts[k] = t1 - t0;
            dt += dts[k];

            if (consumer != null) {
                LOG.info("{} {} consume result:", alternative, impl);
                consumer.accept(result);
            }

            test(alternative, impl, result, expected);
        }
        if (experiments > 1 && LOG.isInfoEnabled()) {
            LOG.info("{} {} dt avg {} ms {} experiments, dts: {} {}", alternative, impl, dt / experiments,
                experiments,
                dt0,
                Arrays.toString(dts));
        }
    }

    private void test(String alternative, String impl, PropertyBags result, Expected expected) {
        if (doAssert) {
            assertEquals(expected.resultSize, result.size());
        } else if (LOG.isInfoEnabled()) {
            LOG.info("{} {} results {} {} {}", alternative, impl, expected, result.size(),
                expected.resultSize == result.size() ? "OK" : "FAIL");
        }
        for (String p : expected.propertyCount.keySet()) {
            long expectedPropertyCount = expected.propertyCount.get(p);
            long actualPropertyCount = result.stream().filter(r -> r.containsKey(p)).count();
            if (doAssert) {
                assertEquals(expectedPropertyCount, actualPropertyCount);
            } else if (LOG.isInfoEnabled()) {
                LOG.info("{} {} {} {} {} {}", alternative, impl, p, expectedPropertyCount, actualPropertyCount,
                    expectedPropertyCount == actualPropertyCount ? "OK" : "FAIL");
            }
        }
    }

    private CgmesModelTripleStore modelFor(String implementation) throws IOException {
        if (cacheModels) {
            return cachedModels.get(implementation);
        } else {
            CgmesModel cgmes = CgmesModelFactory.create(gridModel.dataSource(), implementation);
            assertTrue(cgmes instanceof CgmesModelTripleStore);
            return (CgmesModelTripleStore) cgmes;
        }
    }

    private final List<String> implementations;
    private final boolean cacheModels;
    private final Map<String, CgmesModelTripleStore> cachedModels;
    private final QueryCatalog queries;
    private final GridModelReference gridModel;
    private final Expected expected;
    private final int experiments;
    private final boolean doAssert;
    private final Consumer<PropertyBags> consumer;

    private static final Logger LOG = LoggerFactory.getLogger(AlternativeQueriesTester.class);
}