From eb74eeb82816a021e6b7c01f9e53226e95f1f604 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Mon, 9 Jan 2017 18:55:14 +0300 Subject: [PATCH] test for ANSI coloring performance + fixes --- .../execution/process/AnsiEscapeDecoder.java | 23 +++--- .../process/AnsiEscapeDecoderTest.java | 77 ++++++++++++++++++- .../testFramework/PlatformTestUtil.java | 6 +- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java b/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java index 1547d15f0c3f..3f0030cef70e 100644 --- a/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java +++ b/platform/platform-api/src/com/intellij/execution/process/AnsiEscapeDecoder.java @@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.regex.Pattern; /** * See ANSI escape code. @@ -34,10 +33,11 @@ import java.util.regex.Pattern; public class AnsiEscapeDecoder { private static final char ESC_CHAR = '\u001B'; // Escape sequence start character private static final String CSI = ESC_CHAR + "["; // "Control Sequence Initiator" - private static final Pattern INNER_PATTERN = Pattern.compile(Pattern.quote("m" + CSI)); + private static final String M_CSI = "m" + CSI; private static final char BACKSPACE = '\b'; private Key myCurrentTextAttributes; + private final ColoredOutputTypeRegistry myColoredOutputTypeRegistry = ColoredOutputTypeRegistry.getInstance(); /** * Parses ansi-color codes from text and sends text fragments with color attributes to textAcceptor @@ -48,9 +48,9 @@ public class AnsiEscapeDecoder { * It can implement ColoredChunksAcceptor to receive list of pairs (text, attribute). */ public void escapeText(@NotNull String text, @NotNull Key outputType, @NotNull ColoredTextAcceptor textAcceptor) { - List> chunks = null; - int pos = 0; text = normalizeAsciiControlCharacters(text); + int pos = 0; + List> chunks = null; while (true) { int escSeqBeginInd = text.indexOf(CSI, pos); if (escSeqBeginInd < 0) { @@ -67,8 +67,8 @@ public class AnsiEscapeDecoder { String escSeq = text.substring(escSeqBeginInd, escSeqEndInd); // this is a simple fix for RUBY-8996: // we replace several consecutive escape sequences with one which contains all these sequences - String colorAttribute = INNER_PATTERN.matcher(escSeq).replaceAll(";"); - myCurrentTextAttributes = ColoredOutputTypeRegistry.getInstance().getOutputKey(colorAttribute); + String colorAttribute = StringUtil.replace(escSeq, M_CSI, ";"); + myCurrentTextAttributes = myColoredOutputTypeRegistry.getOutputKey(colorAttribute); } pos = escSeqEndInd; } @@ -189,17 +189,12 @@ public class AnsiEscapeDecoder { return myCurrentTextAttributes != null ? myCurrentTextAttributes : outputType; } - public void coloredTextAvailable(@NotNull List> textChunks, ColoredTextAcceptor textAcceptor) { - for (Pair textChunk : textChunks) { - textAcceptor.coloredTextAvailable(textChunk.getFirst(), textChunk.getSecond()); - } - } - public interface ColoredChunksAcceptor extends ColoredTextAcceptor { - void coloredChunksAvailable(List> chunks); + void coloredChunksAvailable(@NotNull List> chunks); } + @FunctionalInterface public interface ColoredTextAcceptor { - void coloredTextAvailable(String text, Key attributes); + void coloredTextAvailable(@NotNull String text, @NotNull Key attributes); } } diff --git a/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java b/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java index 99d2ce52c87b..13687c20b01c 100644 --- a/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java +++ b/platform/platform-tests/testSrc/com/intellij/execution/process/AnsiEscapeDecoderTest.java @@ -2,14 +2,22 @@ package com.intellij.execution.process; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream; +import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.junit.Assert; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; public class AnsiEscapeDecoderTest extends PlatformTestCase { @@ -79,16 +87,81 @@ public class AnsiEscapeDecoderTest extends PlatformTestCase { private static AnsiEscapeDecoder.ColoredChunksAcceptor createExpectedAcceptor(@NotNull final Pair... expected) { return new AnsiEscapeDecoder.ColoredChunksAcceptor() { @Override - public void coloredChunksAvailable(List> chunks) { + public void coloredChunksAvailable(@NotNull List> chunks) { List> expectedWithKeyName = toListWithKeyName(Arrays.asList(expected)); List> actualWithKeyName = toListWithKeyName(chunks); Assert.assertEquals(expectedWithKeyName, actualWithKeyName); } @Override - public void coloredTextAvailable(String text, Key attributes) { + public void coloredTextAvailable(@NotNull String text, @NotNull Key attributes) { throw new RuntimeException(); // shouldn't be called } }; } + + public void testPerformance() throws IOException { + byte[] buffer = new byte[100000]; + BufferExposingByteArrayOutputStream outputStream = new BufferExposingByteArrayOutputStream(buffer); + BufferExposingByteArrayInputStream inputStream = new BufferExposingByteArrayInputStream(buffer); + AtomicBoolean finished = new AtomicBoolean(); + Process testProcess = new Process() { + @Override + public OutputStream getOutputStream() { + return outputStream; + } + + @Override + public InputStream getInputStream() { + return inputStream; + } + + @Override + public InputStream getErrorStream() { + return inputStream; + } + + @Override + public int waitFor() { + while (!finished.get()); + return 0; + } + + @Override + public int exitValue() { + return 0; + } + + @Override + public void destroy() { + finished.set(true); + } + }; + + withProcessHandlerFrom(testProcess, handler -> { + PlatformTestUtil.startPerformanceTest("ansi color", 15000, ()->{ + for (int i=0; i<2_000_000;i++) { + handler.notifyTextAvailable(i+"Chrome 35.0.1916 (Linux): Executed 0 of 1\u001B[32m SUCCESS\u001B[39m (0 secs / 0 secs)\n", ProcessOutputTypes.STDOUT); + handler.notifyTextAvailable(i+"Plain\u001B[32mGreen\u001B[39mNormal\u001B[1A\u001B[2K\u001B[31mRed\u001B[39m\n", ProcessOutputTypes.SYSTEM); + } + }).assertTiming(); + }); + } + + public static void withProcessHandlerFrom(@NotNull Process testProcess, @NotNull Consumer consumer) { + KillableColoredProcessHandler handler = new KillableColoredProcessHandler(testProcess, "testProcess"); + handler.setShouldDestroyProcessRecursively(false); + handler.startNotify(); + handler.notifyTextAvailable("Running stuff...\n", ProcessOutputTypes.STDOUT); + + try { + consumer.consume(handler); + } + finally { + handler.doDestroyProcess(); + handler.notifyProcessTerminated(0); + handler.waitFor(); + } + + } } diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java index 7fcba408fb9f..1887bf1c587e 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java @@ -601,7 +601,7 @@ public class PlatformTestUtil { String logMessage = message; if (duration > expectedOnMyMachine) { int percentage = (int)(100.0 * (duration - expectedOnMyMachine) / expectedOnMyMachine); - logMessage += ": " + percentage + "% longer"; + logMessage += ": " + "\u001B[31;1m " + percentage + "% longer" + "\u001B[0m"; } logMessage += "\n Expected: " + formatTime(expectedOnMyMachine) + "\n Actual: " + formatTime(duration) + "\n " + Timings.getStatistics(); @@ -634,7 +634,9 @@ public class PlatformTestUtil { System.gc(); String s = "Another epic fail (remaining attempts: " + attempts + "): " + logMessage; TeamCityLogger.warning(s, null); - System.err.println(s); + if (UsefulTestCase.IS_UNDER_TEAMCITY) { + System.err.println(s); + } //if (attempts == 1) { // try { // Class.forName("com.intellij.util.ProfilingUtil").getMethod("startCPUProfiling").invoke(null);