/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */
package org.mockitousage.bugs;

import static org.mockito.Mockito.*;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

// issue 322
// the only evidence of this failing test was shown on a RHEL with IBM J9 JVM 64bits
//
// details
// java.fullversion=JRE 1.6.0 IBM J9 2.6 Linux amd64-64 20111113_94967  (JIT enabled, AOT enabled)
// Linux2.6.32-220.4.2.el6.x86_64 #1SMP Mon Feb 6 16:39:28EST 2012x86_64 x86_64 x86_64 GNU/Linux
public class ConcurrentModificationExceptionOnMultiThreadedVerificationTest {

    int nThreads = 1;

    static final int TIMES = 100;
    static final int INTERVAL_MILLIS = 10;

    ITarget target = Mockito.mock(ITarget.class);
    ExecutorService fixedThreadPool;

    @Before
    public void setUp() {
        target = Mockito.mock(ITarget.class);
        fixedThreadPool = Executors.newFixedThreadPool(nThreads);
    }

    @Test
    public void shouldSuccessfullyVerifyConcurrentInvocationsWithTimeout() throws Exception {
        int potentialOverhead = 1000; // Leave 1000ms extra before timing out as leeway for test overheads
        int expectedMaxTestLength = TIMES * INTERVAL_MILLIS + potentialOverhead;

        reset(target);
        startInvocations();

        verify(target, timeout(expectedMaxTestLength).times(TIMES * nThreads)).targetMethod("arg");
        verifyNoMoreInteractions(target);
    }

    private void startInvocations() throws InterruptedException,
            ExecutionException {

        for(int i=0; i<nThreads; i++) {
            fixedThreadPool.submit(new TargetInvoker(i));
        }

    }

    public class TargetInvoker implements Callable<Object> {
        private final int seq;

        TargetInvoker(int seq) {
            this.seq = seq;
        }

        public Object call() throws Exception {
            System.err.println("started " + seq);
            for (int i = 0; i < TIMES; i++) {
                Thread.yield();
                target.targetMethod("arg");
                Thread.sleep((long) INTERVAL_MILLIS);
            }
            System.err.println("finished" + seq);
            return seq;
        }

    }

    public interface ITarget {
        String targetMethod(String arg);
    }

}
