/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.surefire.junitcore.pc;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
import org.apache.maven.surefire.api.testset.TestSetFailedException;
import org.apache.maven.surefire.junitcore.JUnitCoreParameters;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.rules.ExpectedException;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;

import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.PARALLEL_KEY;
import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.PARALLEL_TIMEOUTFORCED_KEY;
import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.PARALLEL_TIMEOUT_KEY;
import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.THREADCOUNTCLASSES_KEY;
import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.THREADCOUNTMETHODS_KEY;
import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.THREADCOUNTSUITES_KEY;
import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.THREADCOUNT_KEY;
import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.USEUNLIMITEDTHREADS_KEY;
import static org.apache.maven.surefire.junitcore.pc.ParallelComputerUtil.resolveConcurrency;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

/**
 * Testing an algorithm in {@link ParallelComputerUtil} which configures allocated thread resources in ParallelComputer
 * by given {@link org.apache.maven.surefire.junitcore.JUnitCoreParameters}.
 *
 * @author Tibor Digana (tibor17)
 * @see ParallelComputerUtil
 * @since 2.16
 */
@RunWith(Theories.class)
@SuppressWarnings("checkstyle:magicnumber")
public final class ParallelComputerUtilTest {
    private static final ConsoleLogger LOGGER = mock(ConsoleLogger.class);

    @DataPoint
    public static final int CPU_1 = 1;

    @DataPoint
    public static final int CPU_4 = 4;

    @Rule
    public final ExpectedException exception = ExpectedException.none();

    @BeforeClass
    public static void beforeClass() {
        ParallelComputerUtil.overrideAvailableProcessors(1);
    }

    @AfterClass
    public static void afterClass() {
        ParallelComputerUtil.setDefaultAvailableProcessors();
    }

    @Before
    public void beforeTest() throws InterruptedException {
        System.gc();
        Thread.sleep(50L);
        assertFalse(Thread.currentThread().isInterrupted());
    }

    private static Map<String, String> parallel(String parallel) {
        return Collections.singletonMap(PARALLEL_KEY, parallel);
    }

    @Test
    public void unknownParallel() throws TestSetFailedException {
        Map<String, String> properties = new HashMap<>();
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(new JUnitCoreParameters(properties), null);
    }

    @Test
    public void unknownThreadCountSuites() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("suites"));
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Test
    public void unknownThreadCountClasses() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("classes"));
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Test
    public void unknownThreadCountMethods() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("methods"));
        assertFalse(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Test
    public void unknownThreadCountBoth() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("both"));
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Test
    public void unknownThreadCountAll() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("all"));
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Test
    public void unknownThreadCountSuitesAndClasses() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("suitesAndClasses"));
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Test
    public void unknownThreadCountSuitesAndMethods() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("suitesAndMethods"));
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Test
    public void unknownThreadCountClassesAndMethods() throws TestSetFailedException {
        JUnitCoreParameters params = new JUnitCoreParameters(parallel("classesAndMethods"));
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        exception.expect(TestSetFailedException.class);
        resolveConcurrency(params, null);
    }

    @Theory
    public void useUnlimitedThreadsSuites(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suites");
        properties.put(USEUNLIMITEDTHREADS_KEY, "true");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(Integer.MAX_VALUE));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(0));

        properties.put(THREADCOUNTSUITES_KEY, "5");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void useUnlimitedThreadsClasses(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classes");
        properties.put(USEUNLIMITEDTHREADS_KEY, "true");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(Integer.MAX_VALUE));
        assertThat(concurrency.methods, is(0));

        properties.put(THREADCOUNTCLASSES_KEY, "5");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(5 * cpu));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void unlimitedThreadsMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(USEUNLIMITEDTHREADS_KEY, "true");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));

        properties.put(THREADCOUNTMETHODS_KEY, "5");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(5 * cpu));
    }

    @Theory
    public void unlimitedThreadsSuitesAndClasses(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndClasses");
        properties.put(USEUNLIMITEDTHREADS_KEY, "true");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(Integer.MAX_VALUE));
        assertThat(concurrency.classes, is(Integer.MAX_VALUE));
        assertThat(concurrency.methods, is(0));

        properties.put(THREADCOUNTSUITES_KEY, "5");
        properties.put(THREADCOUNTCLASSES_KEY, "15");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(15 * cpu));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void unlimitedThreadsSuitesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndMethods");
        properties.put(USEUNLIMITEDTHREADS_KEY, "true");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(Integer.MAX_VALUE));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));

        properties.put(THREADCOUNTSUITES_KEY, "5");
        properties.put(THREADCOUNTMETHODS_KEY, "15");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(15 * cpu));
    }

    @Theory
    public void unlimitedThreadsClassesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classesAndMethods");
        properties.put(USEUNLIMITEDTHREADS_KEY, "true");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(Integer.MAX_VALUE));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));

        properties.put(THREADCOUNTCLASSES_KEY, "5");
        properties.put(THREADCOUNTMETHODS_KEY, "15");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(5 * cpu));
        assertThat(concurrency.methods, is(15 * cpu));
    }

    @Theory
    public void unlimitedThreadsAll(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "all");
        properties.put(USEUNLIMITEDTHREADS_KEY, "true");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(Integer.MAX_VALUE));
        assertThat(concurrency.classes, is(Integer.MAX_VALUE));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));

        properties.put(THREADCOUNTSUITES_KEY, "5");
        properties.put(THREADCOUNTCLASSES_KEY, "15");
        properties.put(THREADCOUNTMETHODS_KEY, "30");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(15 * cpu));
        assertThat(concurrency.methods, is(30 * cpu));
    }

    @Theory
    public void threadCountSuites(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suites");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(0));
        assertThat(concurrency.suites, is(3 * cpu));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void threadCountClasses(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classes");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(0));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(3 * cpu));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void threadCountMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(0));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(3 * cpu));
    }

    @Theory
    public void threadCountBoth(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "both");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is((int) ((3d / 2) * cpu)));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void threadCountClassesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classesAndMethods");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is((int) ((3d / 2) * cpu)));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void threadCountSuitesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndMethods");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        assertThat(concurrency.suites, is((int) ((3d / 2) * cpu)));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void threadCountSuitesAndClasses(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndClasses");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        assertThat(concurrency.suites, is((int) ((3d / 2) * cpu)));
        assertThat(concurrency.classes, is(Integer.MAX_VALUE));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void threadCountAll(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "all");
        properties.put(THREADCOUNT_KEY, "3");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        assertThat(concurrency.suites, is(cpu));
        assertThat(concurrency.classes, is(cpu));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void everyThreadCountSuitesAndClasses(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndClasses");
        properties.put(THREADCOUNT_KEY, "3");
        // % percentage ratio
        properties.put(THREADCOUNTSUITES_KEY, "34");
        properties.put(THREADCOUNTCLASSES_KEY, "66");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        int concurrentSuites = (int) (0.34d * concurrency.capacity);
        assertThat(concurrency.suites, is(concurrentSuites));
        assertThat(concurrency.classes, is(concurrency.capacity - concurrentSuites));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void everyThreadCountSuitesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndMethods");
        properties.put(THREADCOUNT_KEY, "3");
        // % percentage ratio
        properties.put(THREADCOUNTSUITES_KEY, "34");
        properties.put(THREADCOUNTMETHODS_KEY, "66");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        int concurrentSuites = (int) (0.34d * concurrency.capacity);
        assertThat(concurrency.suites, is(concurrentSuites));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(concurrency.capacity - concurrentSuites));
    }

    @Theory
    public void everyThreadCountClassesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classesAndMethods");
        properties.put(THREADCOUNT_KEY, "3");
        // % percentage ratio
        properties.put(THREADCOUNTCLASSES_KEY, "34");
        properties.put(THREADCOUNTMETHODS_KEY, "66");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        assertThat(concurrency.suites, is(0));
        int concurrentClasses = (int) (0.34d * concurrency.capacity);
        assertThat(concurrency.classes, is(concurrentClasses));
        assertThat(concurrency.methods, is(concurrency.capacity - concurrentClasses));
    }

    @Theory
    public void everyThreadCountAll(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "all");
        properties.put(THREADCOUNT_KEY, "3");
        // % percentage ratio
        properties.put(THREADCOUNTSUITES_KEY, "17");
        properties.put(THREADCOUNTCLASSES_KEY, "34");
        properties.put(THREADCOUNTMETHODS_KEY, "49");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(3 * cpu));
        int concurrentSuites = (int) (0.17d * concurrency.capacity);
        int concurrentClasses = (int) (0.34d * concurrency.capacity);
        assertThat(concurrency.suites, is(concurrentSuites));
        assertThat(concurrency.classes, is(concurrentClasses));
        assertThat(concurrency.methods, is(concurrency.capacity - concurrentSuites - concurrentClasses));
    }

    @Theory
    public void reusableThreadCountSuitesAndClasses(int cpu) throws TestSetFailedException {
        // 4 * cpu to 5 * cpu threads to run test classes
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndClasses");
        properties.put(THREADCOUNT_KEY, "6");
        properties.put(THREADCOUNTSUITES_KEY, "2");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(6 * cpu));
        assertThat(concurrency.suites, is(2 * cpu));
        assertThat(concurrency.classes, is(Integer.MAX_VALUE));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void reusableThreadCountSuitesAndMethods(int cpu) throws TestSetFailedException {
        // 4 * cpu to 5 * cpu threads to run test methods
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndMethods");
        properties.put(THREADCOUNT_KEY, "6");
        properties.put(THREADCOUNTSUITES_KEY, "2");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(6 * cpu));
        assertThat(concurrency.suites, is(2 * cpu));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void reusableThreadCountClassesAndMethods(int cpu) throws TestSetFailedException {
        // 4 * cpu to 5 * cpu threads to run test methods
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classesAndMethods");
        properties.put(THREADCOUNT_KEY, "6");
        properties.put(THREADCOUNTCLASSES_KEY, "2");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(6 * cpu));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(2 * cpu));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void reusableThreadCountAll(int cpu) throws TestSetFailedException {
        // 8 * cpu to 13 * cpu threads to run test methods
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "all");
        properties.put(THREADCOUNT_KEY, "14");
        properties.put(THREADCOUNTSUITES_KEY, "2");
        properties.put(THREADCOUNTCLASSES_KEY, "4");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(14 * cpu));
        assertThat(concurrency.suites, is(2 * cpu));
        assertThat(concurrency.classes, is(4 * cpu));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void suites(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suites");
        properties.put(THREADCOUNTSUITES_KEY, "5");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(5 * cpu));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void classes(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classes");
        properties.put(THREADCOUNTCLASSES_KEY, "5");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(5 * cpu));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(5 * cpu));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void methods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(THREADCOUNTMETHODS_KEY, "5");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(5 * cpu));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(5 * cpu));
    }

    @Theory
    public void suitesAndClasses(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();

        properties.put(PARALLEL_KEY, "suitesAndClasses");
        properties.put(THREADCOUNTSUITES_KEY, "5");
        properties.put(THREADCOUNTCLASSES_KEY, "15");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(20 * cpu));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(15 * cpu));
        assertThat(concurrency.methods, is(0));

        // Warning: this case works but is not enabled in AbstractSurefireMojo
        // Instead use the 'useUnlimitedThreads' parameter.
        properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndClasses");
        properties.put(THREADCOUNTSUITES_KEY, "5");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertFalse(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(Integer.MAX_VALUE));
        assertThat(concurrency.methods, is(0));
    }

    @Theory
    public void suitesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();

        properties.put(PARALLEL_KEY, "suitesAndMethods");
        properties.put(THREADCOUNTSUITES_KEY, "5");
        properties.put(THREADCOUNTMETHODS_KEY, "15");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(20 * cpu));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(15 * cpu));

        // Warning: this case works but is not enabled in AbstractSurefireMojo
        // Instead use the 'useUnlimitedThreads' parameter.
        properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "suitesAndMethods");
        properties.put(THREADCOUNTSUITES_KEY, "5");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertFalse(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(0));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void classesAndMethods(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();

        properties.put(PARALLEL_KEY, "classesAndMethods");
        properties.put(THREADCOUNTCLASSES_KEY, "5");
        properties.put(THREADCOUNTMETHODS_KEY, "15");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(20 * cpu));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(5 * cpu));
        assertThat(concurrency.methods, is(15 * cpu));

        // Warning: this case works but is not enabled in AbstractSurefireMojo
        // Instead use the 'useUnlimitedThreads' parameter.
        properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "classesAndMethods");
        properties.put(THREADCOUNTCLASSES_KEY, "5");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertFalse(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(0));
        assertThat(concurrency.classes, is(5 * cpu));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Theory
    public void all(int cpu) throws TestSetFailedException {
        ParallelComputerUtil.overrideAvailableProcessors(cpu);
        Map<String, String> properties = new HashMap<>();

        properties.put(PARALLEL_KEY, "all");
        properties.put(THREADCOUNTSUITES_KEY, "5");
        properties.put(THREADCOUNTCLASSES_KEY, "15");
        properties.put(THREADCOUNTMETHODS_KEY, "30");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        Concurrency concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(50 * cpu));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(15 * cpu));
        assertThat(concurrency.methods, is(30 * cpu));

        // Warning: these cases work but they are not enabled in AbstractSurefireMojo
        // Instead use the 'useUnlimitedThreads' parameter.
        properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "all");
        properties.put(THREADCOUNTSUITES_KEY, "5");
        properties.put(THREADCOUNTCLASSES_KEY, "15");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(5 * cpu));
        assertThat(concurrency.classes, is(15 * cpu));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));

        properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "all");
        properties.put(THREADCOUNTCLASSES_KEY, "15");
        params = new JUnitCoreParameters(properties);
        concurrency = resolveConcurrency(params, null);
        assertTrue(params.isParallelSuites());
        assertTrue(params.isParallelClasses());
        assertTrue(params.isParallelMethods());
        assertThat(concurrency.capacity, is(Integer.MAX_VALUE));
        assertThat(concurrency.suites, is(Integer.MAX_VALUE));
        assertThat(concurrency.classes, is(15 * cpu));
        assertThat(concurrency.methods, is(Integer.MAX_VALUE));
    }

    @Test
    public void withoutShutdown() {
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(THREADCOUNTMETHODS_KEY, "2");
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        ParallelComputerBuilder pcBuilder = new ParallelComputerBuilder(LOGGER, params);
        ParallelComputer pc = pcBuilder.buildComputer();
        final JUnitCore core = new JUnitCore();
        final long t1 = systemMillis();
        final Result result = core.run(pc, TestClass.class);
        final long t2 = systemMillis();
        long timeSpent = t2 - t1;
        final long deltaTime = 500L;

        assertTrue(result.wasSuccessful());
        assertThat(result.getRunCount(), is(3));
        assertThat(result.getFailureCount(), is(0));
        assertThat(result.getIgnoreCount(), is(0));
        // assertThat( timeSpent, between (timeSpent - deltaTime, timeSpent + deltaTime + 2000L ) );
        assertEquals(10000L, timeSpent, deltaTime);
    }

    @Test
    public void shutdown() throws TestSetFailedException {
        // The JUnitCore returns after 2.5s.
        // The test-methods in TestClass are NOT interrupted, and return normally after 5s.
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(THREADCOUNTMETHODS_KEY, "2");
        properties.put(PARALLEL_TIMEOUT_KEY, Double.toString(2.5d));
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        ParallelComputerBuilder pcBuilder = new ParallelComputerBuilder(LOGGER, params);
        ParallelComputer pc = pcBuilder.buildComputer();
        final JUnitCore core = new JUnitCore();
        final long t1 = systemMillis();
        core.run(pc, TestClass.class);
        final long t2 = systemMillis();
        final long timeSpent = t2 - t1;
        final long deltaTime = 500L;

        assertEquals(5000L, timeSpent, deltaTime);
        String description = pc.describeElapsedTimeout();
        assertTrue(description.contains("The test run has finished abruptly after timeout of 2.5 seconds."));
        assertTrue(description.contains(
                "These tests were executed in prior to the shutdown operation:\n" + TestClass.class.getName()));
    }

    @Test
    public void forcedShutdown() throws TestSetFailedException {
        // The JUnitCore returns after 2.5s, and the test-methods in TestClass are interrupted.
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(THREADCOUNTMETHODS_KEY, "2");
        properties.put(PARALLEL_TIMEOUTFORCED_KEY, Double.toString(2.5d));
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        ParallelComputerBuilder pcBuilder = new ParallelComputerBuilder(LOGGER, params);
        ParallelComputer pc = pcBuilder.buildComputer();
        final JUnitCore core = new JUnitCore();
        final long t1 = systemMillis();
        core.run(pc, TestClass.class);
        final long t2 = systemMillis();
        final long timeSpent = t2 - t1;
        final long deltaTime = 500L;

        assertEquals(2500L, timeSpent, deltaTime);
        String description = pc.describeElapsedTimeout();
        assertTrue(description.contains("The test run has finished abruptly after timeout of 2.5 seconds."));
        assertTrue(description.contains(
                "These tests were executed in prior to the shutdown operation:\n" + TestClass.class.getName()));
    }

    @Test
    public void timeoutAndForcedShutdown() throws TestSetFailedException {
        // The JUnitCore returns after 3.5s and the test-methods in TestClass are timed out after 2.5s.
        // No new test methods are scheduled for execution after 2.5s.
        // Interruption of test methods after 3.5s.
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(THREADCOUNTMETHODS_KEY, "2");
        properties.put(PARALLEL_TIMEOUT_KEY, Double.toString(2.5d));
        properties.put(PARALLEL_TIMEOUTFORCED_KEY, Double.toString(3.5d));
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        ParallelComputerBuilder pcBuilder = new ParallelComputerBuilder(LOGGER, params);
        ParallelComputer pc = pcBuilder.buildComputer();
        final JUnitCore core = new JUnitCore();
        final long t1 = systemMillis();
        core.run(pc, TestClass.class);
        final long t2 = systemMillis();
        final long timeSpent = t2 - t1;
        final long deltaTime = 500L;

        assertEquals(3500L, timeSpent, deltaTime);
        String description = pc.describeElapsedTimeout();
        assertTrue(description.contains("The test run has finished abruptly after timeout of 2.5 seconds."));
        assertTrue(description.contains(
                "These tests were executed in prior to the shutdown operation:\n" + TestClass.class.getName()));
    }

    @Test
    public void forcedTimeoutAndShutdown() throws Exception {
        // The JUnitCore returns after 3.5s and the test-methods in TestClass are interrupted after 3.5s.
        Map<String, String> properties = new HashMap<>();
        properties.put(PARALLEL_KEY, "methods");
        properties.put(THREADCOUNTMETHODS_KEY, "2");
        properties.put(PARALLEL_TIMEOUTFORCED_KEY, Double.toString(3.5d));
        properties.put(PARALLEL_TIMEOUT_KEY, Double.toString(4.0d));
        JUnitCoreParameters params = new JUnitCoreParameters(properties);
        ParallelComputerBuilder pcBuilder = new ParallelComputerBuilder(LOGGER, params);
        ParallelComputer pc = pcBuilder.buildComputer();
        final JUnitCore core = new JUnitCore();
        final long t1 = systemMillis();
        core.run(pc, TestClass.class);
        final long t2 = systemMillis();
        final long timeSpent = t2 - t1;
        final long deltaTime = 500L;

        assertEquals(3500L, timeSpent, deltaTime);
        String description = pc.describeElapsedTimeout();
        assertTrue(description.contains("The test run has finished abruptly after timeout of 3.5 seconds."));
        assertTrue(description.contains(
                "These tests were executed in prior to the shutdown operation:\n" + TestClass.class.getName()));
    }

    /**
     *
     */
    public static class TestClass {
        @Test
        public void a() throws InterruptedException {
            long t1 = systemMillis();
            try {
                Thread.sleep(5000L);
            } finally {
                System.out.println(getClass().getSimpleName() + "#a() spent " + (systemMillis() - t1));
            }
        }

        @Test
        public void b() throws InterruptedException {
            long t1 = systemMillis();
            try {
                Thread.sleep(5000L);
            } finally {
                System.out.println(getClass().getSimpleName() + "#b() spent " + (systemMillis() - t1));
            }
        }

        @Test
        public void c() throws InterruptedException {
            long t1 = systemMillis();
            try {
                Thread.sleep(5000L);
            } finally {
                System.out.println(getClass().getSimpleName() + "#c() spent " + (systemMillis() - t1));
            }
        }
    }

    private static long systemMillis() {
        return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
    }
}
