test for ANSI coloring performance + fixes

This commit is contained in:
Alexey Kudravtsev
2017-01-10 14:52:04 +03:00
parent 66bf61455a
commit eb74eeb828
3 changed files with 88 additions and 18 deletions
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.regex.Pattern;
/**
* See <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape code</a>.
@@ -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<Pair<String, Key>> chunks = null;
int pos = 0;
text = normalizeAsciiControlCharacters(text);
int pos = 0;
List<Pair<String, Key>> 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<Pair<String, Key>> textChunks, ColoredTextAcceptor textAcceptor) {
for (Pair<String, Key> textChunk : textChunks) {
textAcceptor.coloredTextAvailable(textChunk.getFirst(), textChunk.getSecond());
}
}
public interface ColoredChunksAcceptor extends ColoredTextAcceptor {
void coloredChunksAvailable(List<Pair<String, Key>> chunks);
void coloredChunksAvailable(@NotNull List<Pair<String, Key>> chunks);
}
@FunctionalInterface
public interface ColoredTextAcceptor {
void coloredTextAvailable(String text, Key attributes);
void coloredTextAvailable(@NotNull String text, @NotNull Key attributes);
}
}
@@ -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<String, Key>... expected) {
return new AnsiEscapeDecoder.ColoredChunksAcceptor() {
@Override
public void coloredChunksAvailable(List<Pair<String, Key>> chunks) {
public void coloredChunksAvailable(@NotNull List<Pair<String, Key>> chunks) {
List<Pair<String, String>> expectedWithKeyName = toListWithKeyName(Arrays.asList(expected));
List<Pair<String, String>> 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<ProcessHandler> 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();
}
}
}
@@ -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);