package org.apache.maven.surefire.junitcore.pc;

/*
 * 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.
 */

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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

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() );
    }
}
