mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
WI-46570: Do not break long TC message attributes.
* There should be one place to cut long messages, no need several places * One can't simply cut part of TC service message. We can only cut part of one of it's values So, we parse message, look for longest attribute value and cut this value. Message is valid and consistent that way. GitOrigin-RevId: bc5a0142e03483a1566e158e94fe808b054919a4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a724467ad1
commit
f4ed78c8f5
+72
@@ -0,0 +1,72 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.execution.testframework.sm.runner
|
||||
|
||||
import com.intellij.execution.impl.ConsoleBuffer
|
||||
import jetbrains.buildServer.messages.serviceMessages.ServiceMessage
|
||||
|
||||
private const val ELLIPSIS = "<...>"
|
||||
|
||||
private val FIELDS_NOT_TO_TOUCH = setOf("name", "duration", "type", "flowId", "nodeId", "parentNodeId")
|
||||
private val EXPECTED_ACTUAL = arrayOf("expected", "actual")
|
||||
|
||||
|
||||
/**
|
||||
* If [text] is longer than [maxLength] cut it and insert [ELLIPSIS] in cut.
|
||||
* If it is a [ServiceMessage], then find longest attribute and cut it leaving [margin] as prefix and postfix
|
||||
* [EXPECTED_ACTUAL] attrs both are cut.
|
||||
*
|
||||
*/
|
||||
fun cutLineIfTooLong(text: String, maxLength: Int = ConsoleBuffer.getCycleBufferSize(), margin: Int = 1000): String {
|
||||
val minValueLengthToCut = (margin * 2) + ELLIPSIS.length
|
||||
if (text.length <= maxLength || maxLength < minValueLengthToCut) {
|
||||
return text
|
||||
}
|
||||
|
||||
val message = ServiceMessage.parse(text.trim())
|
||||
|
||||
if (message == null) {
|
||||
//Not a message, cut as regular text
|
||||
return text.substring(0, maxLength - ELLIPSIS.length) + ELLIPSIS
|
||||
}
|
||||
|
||||
val attributes = HashMap(message.attributes)
|
||||
val attributesToCut = attributes
|
||||
.filter { it.key !in FIELDS_NOT_TO_TOUCH }
|
||||
.toList()
|
||||
.sortedByDescending { it.second.length }
|
||||
.map { it.first }
|
||||
|
||||
val shortener = Shortener(attributes, text.length, margin, minValueLengthToCut)
|
||||
for (attr in attributesToCut) {
|
||||
if (shortener.currentLength < maxLength) {
|
||||
break
|
||||
}
|
||||
shortener.shortenAttribute(attr)
|
||||
if (attr in EXPECTED_ACTUAL) {
|
||||
EXPECTED_ACTUAL.forEach(shortener::shortenAttribute)
|
||||
}
|
||||
}
|
||||
return ServiceMessage.asString(message.messageName, attributes)
|
||||
}
|
||||
|
||||
private class Shortener(private val attributes: MutableMap<String, String>,
|
||||
var currentLength: Int,
|
||||
private val minValueLengthToCut: Int,
|
||||
private val margin: Int) {
|
||||
private val shortened = mutableSetOf<String>()
|
||||
fun shortenAttribute(attribute: String) {
|
||||
if (attribute in shortened) {
|
||||
return
|
||||
}
|
||||
val value = attributes[attribute] ?: return
|
||||
if (value.length <= minValueLengthToCut) { // Tool short to cut
|
||||
return
|
||||
}
|
||||
|
||||
val lenBefore = value.length
|
||||
val newValue = StringBuilder(value).replace(margin, value.length - margin, ELLIPSIS).toString()
|
||||
currentLength -= (lenBefore - newValue.length)
|
||||
attributes[attribute] = newValue
|
||||
shortened.add(attribute)
|
||||
}
|
||||
}
|
||||
+2
-13
@@ -34,7 +34,6 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
*/
|
||||
abstract class OutputEventSplitter(private val bufferTextUntilNewLine: Boolean = false) {
|
||||
|
||||
private val currentCyclicBufferSize = ConsoleBuffer.getCycleBufferSize()
|
||||
private val prevRefs: Map<ProcessOutputType, AtomicReference<Output>> =
|
||||
listOf(ProcessOutputType.STDOUT, ProcessOutputType.STDERR, ProcessOutputType.SYSTEM)
|
||||
.map { it to AtomicReference<Output>() }.toMap()
|
||||
@@ -146,21 +145,11 @@ abstract class OutputEventSplitter(private val bufferTextUntilNewLine: Boolean =
|
||||
}
|
||||
|
||||
private fun flushInternal(text: String, key: Key<*>) {
|
||||
var result = text
|
||||
// Cut long lines
|
||||
if (USE_CYCLE_BUFFER &&
|
||||
text.length > currentCyclicBufferSize &&
|
||||
currentCyclicBufferSize > 2 * SM_MESSAGE_PREFIX) {
|
||||
result = text.substring(0, SM_MESSAGE_PREFIX) + text.substring(text.length - SM_MESSAGE_PREFIX)
|
||||
}
|
||||
|
||||
onTextAvailable(result, key)
|
||||
val textToAdd = if (USE_CYCLE_BUFFER) cutLineIfTooLong(text) else text
|
||||
onTextAvailable(textToAdd, key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal const val SM_MESSAGE_PREFIX = 105
|
||||
|
||||
private val USE_CYCLE_BUFFER = ConsoleBuffer.useCycleBuffer()
|
||||
private const val SERVICE_MESSAGE_START: String = ServiceMessage.SERVICE_MESSAGE_START
|
||||
private const val NEW_LINE: Char = '\n'
|
||||
|
||||
+1
-10
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.execution.testframework.sm.runner;
|
||||
|
||||
import com.intellij.execution.impl.ConsoleBuffer;
|
||||
import com.intellij.execution.process.ColoredOutputTypeRegistry;
|
||||
import com.intellij.execution.process.ProcessOutputTypes;
|
||||
import com.intellij.execution.testframework.TestConsoleProperties;
|
||||
@@ -31,7 +30,6 @@ import static com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerE
|
||||
*/
|
||||
public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer {
|
||||
private static final Logger LOG = Logger.getInstance(OutputToGeneralTestEventsConverter.class.getName());
|
||||
private static final boolean USE_CYCLE_BUFFER = ConsoleBuffer.useCycleBuffer();
|
||||
|
||||
private final MyServiceMessageVisitor myServiceMessageVisitor;
|
||||
private final String myTestFrameworkName;
|
||||
@@ -40,8 +38,7 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
private volatile GeneralTestEventsProcessor myProcessor;
|
||||
private Runnable myTestingStartedHandler;
|
||||
private boolean myFirstTestingStartedEvent = true;
|
||||
private static final String ELLIPSIS = "<...>";
|
||||
private final int myCycleBufferSize = ConsoleBuffer.getCycleBufferSize();
|
||||
|
||||
|
||||
public OutputToGeneralTestEventsConverter(@NotNull final String testFrameworkName, @NotNull final TestConsoleProperties consoleProperties) {
|
||||
// If console is editable, user may want to see output before new line char.
|
||||
@@ -103,12 +100,6 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
|
||||
}
|
||||
|
||||
protected void processConsistentText(@NotNull String text, @NotNull final Key<?> outputType) {
|
||||
if (USE_CYCLE_BUFFER && text.length() > myCycleBufferSize && myCycleBufferSize > OutputEventSplitterKt.SM_MESSAGE_PREFIX) {
|
||||
text = text.substring(0, myCycleBufferSize - OutputEventSplitterKt.SM_MESSAGE_PREFIX) +
|
||||
ELLIPSIS +
|
||||
text.substring(text.length() - OutputEventSplitterKt.SM_MESSAGE_PREFIX + ELLIPSIS.length());
|
||||
}
|
||||
|
||||
try {
|
||||
if (!processServiceMessages(text, outputType, myServiceMessageVisitor)) {
|
||||
//fire current output
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.execution.testframework.sm
|
||||
|
||||
import com.intellij.execution.testframework.sm.runner.cutLineIfTooLong
|
||||
import jetbrains.buildServer.messages.serviceMessages.ServiceMessage
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
class LongLineCutterTest {
|
||||
@Test
|
||||
fun shortMessageUntouched() {
|
||||
val message = ServiceMessage.asString("foo", mapOf(
|
||||
"A" to "B",
|
||||
"Z" to "Q"
|
||||
))
|
||||
Assert.assertEquals(message, cutLineIfTooLong(message, Int.MAX_VALUE, 100))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun longLineShortened() {
|
||||
val maxLength = 10000
|
||||
val text = cutLineIfTooLong("abcde".repeat(maxLength), maxLength, 100)
|
||||
Assert.assertEquals(text.length, maxLength)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun actualExpectedShort() {
|
||||
val maxLength = 1000
|
||||
val message = ServiceMessage.asString("testFailed", mapOf(
|
||||
"expected" to "A".repeat(maxLength * 2),
|
||||
"actual" to "B"
|
||||
));
|
||||
val result = ServiceMessage.parse(cutLineIfTooLong(message, maxLength, 10))!!
|
||||
|
||||
val actual = result.attributes["actual"]!!
|
||||
val expected = result.attributes["expected"]!!
|
||||
|
||||
Assert.assertEquals(actual, "B")
|
||||
Assert.assertTrue(expected.startsWith("A"))
|
||||
Assert.assertTrue(expected.endsWith("A"))
|
||||
Assert.assertTrue("..." in expected)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun actualExpectedLong() {
|
||||
val maxLength = 1000
|
||||
val message = ServiceMessage.asString("testFailed", mapOf(
|
||||
"expected" to "A".repeat(maxLength * 2),
|
||||
"actual" to "B".repeat(maxLength * 2)
|
||||
));
|
||||
val result = ServiceMessage.parse(cutLineIfTooLong(message, maxLength, 10))!!
|
||||
|
||||
val actual = result.attributes["actual"]!!
|
||||
val expected = result.attributes["expected"]!!
|
||||
|
||||
Assert.assertTrue(actual.startsWith("B"))
|
||||
Assert.assertTrue(actual.endsWith("B"))
|
||||
Assert.assertTrue(expected.startsWith("A"))
|
||||
Assert.assertTrue(expected.endsWith("A"))
|
||||
Assert.assertTrue(expected.length == actual.length)
|
||||
Assert.assertTrue("..." in actual)
|
||||
Assert.assertTrue("..." in expected)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun longMessageShortened() {
|
||||
val maxLength = 10000
|
||||
val s = "abc\r\n"
|
||||
val longString = s.repeat(maxLength * 2)
|
||||
val message = ServiceMessage.asString("foo", mapOf(
|
||||
"A" to "B",
|
||||
"C" to "D",
|
||||
"Z" to longString
|
||||
))
|
||||
val result = cutLineIfTooLong(message, maxLength, 100)
|
||||
Assert.assertTrue("Failed to cut message", result.length <= maxLength)
|
||||
val shortenedMessage = ServiceMessage.parse(result)!!
|
||||
Assert.assertEquals("B", shortenedMessage.attributes["A"])
|
||||
Assert.assertEquals("D", shortenedMessage.attributes["C"])
|
||||
val longestValue = shortenedMessage.attributes["Z"]!!
|
||||
Assert.assertTrue("D", longestValue.startsWith(s) && longestValue.endsWith(s))
|
||||
}
|
||||
}
|
||||
+28
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.intellij.execution.testframework.sm;
|
||||
|
||||
import com.intellij.execution.impl.ConsoleBuffer;
|
||||
import com.intellij.execution.process.ProcessOutputType;
|
||||
import com.intellij.execution.process.ProcessOutputTypes;
|
||||
import com.intellij.execution.testframework.sm.runner.OutputEventSplitter;
|
||||
@@ -30,6 +31,7 @@ import org.hamcrest.core.IsCollectionContaining;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -72,6 +74,32 @@ public class OutputLineSplitterTest extends LightPlatformTestCase {
|
||||
};
|
||||
}
|
||||
|
||||
public void testLongText() {
|
||||
final int maxSize = ConsoleBuffer.getCycleBufferSize();
|
||||
final String string = "abc";
|
||||
final String longString = StringUtil.repeat(string, maxSize);
|
||||
mySplitter.process(longString, ProcessOutputType.STDOUT);
|
||||
final String shortenedLine = myOutput.get(ProcessOutputTypes.STDOUT).toList().get(0);
|
||||
Assert.assertEquals(shortenedLine.length(), maxSize);
|
||||
Assert.assertTrue(shortenedLine.startsWith(string));
|
||||
}
|
||||
|
||||
public void testLongMessage() throws ParseException {
|
||||
final int maxSize = ConsoleBuffer.getCycleBufferSize();
|
||||
final String string = "abc|n";
|
||||
final String longString = StringUtil.repeat(string, maxSize);
|
||||
final String junk = "QWE";
|
||||
final String message =
|
||||
String.format("##teamcity[testFailed name='someTest' expected='%s' actual='%s']\n", longString, longString.replaceFirst("abc", junk));
|
||||
|
||||
mySplitter.process(message, ProcessOutputType.STDOUT);
|
||||
final String shortenedLine = myOutput.get(ProcessOutputTypes.STDOUT).toList().get(0);
|
||||
final ServiceMessage shortenedMessage = ServiceMessage.parse(shortenedLine);
|
||||
Assert.assertTrue("Failed to shorten message", shortenedMessage.toString().length() <= maxSize);
|
||||
final Map<String, String> attrs = shortenedMessage.getAttributes();
|
||||
Assert.assertEquals(attrs.get("expected").replaceFirst("abc", junk), attrs.get("actual"));
|
||||
}
|
||||
|
||||
|
||||
public void testMessageEndFlush() {
|
||||
final String text = "hello##";
|
||||
|
||||
Reference in New Issue
Block a user