/* * Copyright 2000-2017 JetBrains s.r.o. * * Licensed 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 com.intellij.util.concurrency; import com.intellij.openapi.Disposable; import com.intellij.openapi.util.Disposer; import org.junit.Assert; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import static javax.swing.SwingUtilities.isEventDispatchThread; /** * @author Sergey.Malenkov */ public class InvokerTest { @Test public void testValidOnEDT() { Disposable parent = InvokerTest::dispose; testValidThread(parent, new Invoker.EDT(parent)); } @Test public void testValidBgPool() { Disposable parent = InvokerTest::dispose; testValidThread(parent, new Invoker.BackgroundPool(parent)); } @Test public void testValidBgThread() { Disposable parent = InvokerTest::dispose; testValidThread(parent, new Invoker.BackgroundThread(parent)); } private static void testValidThread(Disposable parent, Invoker invoker) { CountDownLatch latch = new CountDownLatch(1); test(parent, invoker, latch, error -> countDown(latch, 0, error, "task on invalid thread", invoker::isValidThread)); } @Test public void testInvokeLaterOnEDT() { Disposable parent = InvokerTest::dispose; testInvokeLater(parent, new Invoker.EDT(parent)); } @Test public void testInvokeLaterOnBgPool() { Disposable parent = InvokerTest::dispose; testInvokeLater(parent, new Invoker.BackgroundPool(parent)); } @Test public void testInvokeLaterOnBgThread() { Disposable parent = InvokerTest::dispose; testInvokeLater(parent, new Invoker.BackgroundThread(parent)); } private static void testInvokeLater(Disposable parent, Invoker invoker) { CountDownLatch latch = new CountDownLatch(1); test(parent, invoker, latch, error -> { AtomicBoolean current = new AtomicBoolean(false); invoker.invokeLater(() -> countDown(latch, 100, error, "task is not done before subtask", current::get)); current.set(true); }); } @Test public void testInvokeLaterIfNeededOnEDT() { Disposable parent = InvokerTest::dispose; testInvokeLaterIfNeeded(parent, new Invoker.EDT(parent)); } @Test public void testInvokeLaterIfNeededOnBgPool() { Disposable parent = InvokerTest::dispose; testInvokeLaterIfNeeded(parent, new Invoker.BackgroundPool(parent)); } @Test public void testInvokeLaterIfNeededOnBgThread() { Disposable parent = InvokerTest::dispose; testInvokeLaterIfNeeded(parent, new Invoker.BackgroundThread(parent)); } private static void testInvokeLaterIfNeeded(Disposable parent, Invoker invoker) { CountDownLatch latch = new CountDownLatch(1); test(parent, invoker, latch, error -> { AtomicBoolean current = new AtomicBoolean(true); invoker.invokeLaterIfNeeded(() -> countDown(latch, 100, error, "task is done before subtask", current::get)); current.set(false); }); } @Test public void testQueueOnEDT() { Disposable parent = InvokerTest::dispose; testQueue(parent, new Invoker.EDT(parent), true); } @Test public void testQueueOnBgPool() { Disposable parent = InvokerTest::dispose; testQueue(parent, new Invoker.BackgroundPool(parent), false); } @Test public void testQueueOnBgThread() { Disposable parent = InvokerTest::dispose; testQueue(parent, new Invoker.BackgroundThread(parent), true); } private static void testQueue(Disposable parent, Invoker invoker, boolean ordered) { CountDownLatch latch = new CountDownLatch(2); test(parent, invoker, latch, error -> { long first = ordered ? 2 : 1; invoker.invokeLater(() -> countDown(latch, 100, error, "unexpected task order", () -> first == latch.getCount())); long second = ordered ? 1 : 2; invoker.invokeLater(() -> countDown(latch, 0, error, "unexpected task order", () -> second == latch.getCount())); }); } @Test public void testThreadChangingOnEDT() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.EDT(parent)); } @Test public void testThreadChangingOnBgPool() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundPool(parent)); } @Test public void testThreadChangingOnBgThread() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundThread(parent)); } private static void testThreadChanging(Disposable parent, Invoker invoker) { testThreadChanging(parent, invoker, invoker, true); } @Test public void testThreadChangingOnEDTfromEDT() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.EDT(parent), new Invoker.EDT(parent), true); } @Test public void testThreadChangingOnEDTfromBgPool() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.EDT(parent), new Invoker.BackgroundPool(parent), false); } @Test public void testThreadChangingOnEDTfromBgThread() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.EDT(parent), new Invoker.BackgroundThread(parent), false); } @Test public void testThreadChangingOnBgPoolFromEDT() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundPool(parent), new Invoker.EDT(parent), false); } @Test public void testThreadChangingOnBgPoolFromBgPool() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundPool(parent), new Invoker.BackgroundPool(parent), true); } @Test public void testThreadChangingOnBgPoolFromBgThread() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundPool(parent), new Invoker.BackgroundThread(parent), true); } @Test public void testThreadChangingOnBgThreadFromEDT() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundThread(parent), new Invoker.EDT(parent), false); } @Test public void testThreadChangingOnBgThreadFromBgPool() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundThread(parent), new Invoker.BackgroundPool(parent), null); } @Test public void testThreadChangingOnBgThreadFromBgThread() { Disposable parent = InvokerTest::dispose; testThreadChanging(parent, new Invoker.BackgroundThread(parent), new Invoker.BackgroundThread(parent), null); } private static void testThreadChanging(Disposable parent, Invoker foreground, Invoker background, Boolean equal) { CountDownLatch latch = new CountDownLatch(1); test(parent, foreground, latch, error -> new Command.Processor(foreground, background).process(Thread::currentThread, thread -> countDown(latch, 0, error, "unexpected thread", () -> isExpected(thread, equal)))); } private static boolean isExpected(Thread thread, Boolean equal) { if (equal != null) return equal.equals(thread == Thread.currentThread()); return true; // debug only: thread may be reused } private static void test(Disposable parent, Invoker invoker, CountDownLatch latch, Consumer<AtomicReference<String>> consumer) { Assert.assertFalse("EDT should not be used to start this test", invoker instanceof Invoker.EDT && isEventDispatchThread()); AtomicReference<String> error = new AtomicReference<>(); invoker.invokeLater(() -> consumer.accept(error)); String message; try { latch.await(); message = error.get(); } catch (InterruptedException ignore) { message = "interrupted exception"; } finally { Disposer.dispose(parent); } if (message != null) Assert.fail(message + " @ " + invoker); } private static void countDown(CountDownLatch latch, long ms, AtomicReference<String> error, String message, BooleanSupplier success) { try { if (ms > 0) Thread.sleep(ms); } catch (InterruptedException ignore) { } finally { if (!success.getAsBoolean()) error.set(message); latch.countDown(); } } private static void dispose() { } }