/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation..  All rights reserved.
*/

package mondrian.test;

import mondrian.olap.*;
import mondrian.server.monitor.Monitor;
import mondrian.server.monitor.ServerInfo;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * Cunning tests to discover whether the cache manager is working to spec
 * and is thread-safe.
 *
 * @author Julian Hyde
 */
public class CacheTest extends FoodMartTestCase {
    /**
     * Tests that if N queries are executed at the same time, only one segment
     * request will be sent. The query that arrives second should see that there
     * is a pending segment in the aggregation manager, and should wait for
     * that.
     *
     * <p>If the test fails, look at segmentCreateViaSqlCount. If it has
     * increased by more than one between before and after, the clients have not
     * managed to share work. If it has not increased, the cache was probably
     * not flushed correctly.</p>
     */
    public void testNQueriesWaitingForSameSegmentRepeat()
        throws ExecutionException, InterruptedException
    {
        final int parallel = 10;
        final ThreadPoolExecutor executor =
            new ThreadPoolExecutor(
                2, parallel, 1, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(parallel * 2));
        final int repeatCount = 20;
        for (int i = 0; i < repeatCount; i++) {
            checkNQueriesWaitingForSameSegment(
                executor, parallel, "iteration #" + i + " of " + repeatCount);
        }
        executor.shutdown();
    }

    private void checkNQueriesWaitingForSameSegment(
        ThreadPoolExecutor executor,
        int parallel,
        String iteration)
        throws InterruptedException, ExecutionException
    {
        final MondrianServer server =
            MondrianServer.forConnection(getConnection());
        final CacheControl cacheControl =
            getConnection().getCacheControl(null);
        cacheControl.flush(
            cacheControl.createMeasuresRegion(
                getCubeWithName(
                    "Sales",
                    getConnection().getSchema().getCubes())));
        Thread.sleep(2000); // wait for flush to propagate
        final Monitor monitor = server.getMonitor();
        final ServerInfo serverBefore = monitor.getServer();

        final List<Future<Boolean>> futures = new ArrayList<Future<Boolean>>();
        for (int i = 0; i < parallel; i++) {
            Callable<Boolean> runnable = new Callable<Boolean>() {
                public Boolean call() {
                    assertQueryReturns(
                        "select [Gender].Children * [Product].Children on 0\n"
                        + "from [Sales]",
                        "Axis #0:\n"
                        + "{}\n"
                        + "Axis #1:\n"
                        + "{[Gender].[F], [Product].[Drink]}\n"
                        + "{[Gender].[F], [Product].[Food]}\n"
                        + "{[Gender].[F], [Product].[Non-Consumable]}\n"
                        + "{[Gender].[M], [Product].[Drink]}\n"
                        + "{[Gender].[M], [Product].[Food]}\n"
                        + "{[Gender].[M], [Product].[Non-Consumable]}\n"
                        + "Row #0: 12,202\n"
                        + "Row #0: 94,814\n"
                        + "Row #0: 24,542\n"
                        + "Row #0: 12,395\n"
                        + "Row #0: 97,126\n"
                        + "Row #0: 25,694\n");
                    return true;
                }
            };
            futures.add(executor.submit(runnable));
        }
        for (Future<Boolean> future : futures) {
            assertTrue(future.get() == Boolean.TRUE);
        }
        final ServerInfo serverAfter = monitor.getServer();
        final String beforeAfter =
            "before: " + serverBefore + "\n"
            + "after: " + serverAfter + "\n"
            + iteration;
        assertTrue(
            beforeAfter,
            serverAfter.segmentCreateCount
            == serverBefore.segmentCreateCount + 1
            && serverAfter.segmentCreateViaSqlCount
               == serverBefore.segmentCreateViaSqlCount + 1);
    }

    private Cube getCubeWithName(String cubeName, Cube[] cubes) {
        for (Cube cube : cubes) {
            if (cubeName.equals(cube.getName())) {
                return cube;
            }
        }
        return null;
    }
}

// End CacheTest.java
