From a05fc43fcb7700a35fa19bfb4d6caf5732327d31 Mon Sep 17 00:00:00 2001 From: Sergey Malenkov Date: Wed, 25 Jan 2017 17:45:05 +0300 Subject: [PATCH] add test for multi-thread execution --- .../util/concurrency/InvokerTest.java | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 platform/platform-tests/testSrc/com/intellij/util/concurrency/InvokerTest.java diff --git a/platform/platform-tests/testSrc/com/intellij/util/concurrency/InvokerTest.java b/platform/platform-tests/testSrc/com/intellij/util/concurrency/InvokerTest.java new file mode 100644 index 000000000000..949fb4d4fc6e --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/util/concurrency/InvokerTest.java @@ -0,0 +1,170 @@ +/* + * 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; + +/** + * @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())); + }); + } + + + private static void test(Disposable parent, Invoker invoker, CountDownLatch latch, Consumer> consumer) { + AtomicReference 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 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() { + } +}