以下测试总是在Windows上的IntelliJ 2017.3中的Spock 1.1-groovy-2.4中传递给我:
def "broken assertion"() {
expect:
[('a' * 600_000)].first() == 'b'
}
更令人惊讶的是,以下内容未按预期失败:
def "broken assertion"() {
expect:
[('a' * 600_000)].first().equals('b')
}
错误讯息:
[('a' * 600_000)].first().equals('b')
| | |
| | false
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
此外,从命令行运行时,测试失败。它仅在IntelliJ IDEA中传递。
如何解释这个问题?
修改
在idea.log
中,我看到以下异常:
2018-01-30 12:54:49,518 [1917092061] ERROR - utToGeneralTestEventsConverter - [JUnit]: Error parsing text: [##teamcity[testFailed name='ListTessSpaceNexusVersionsTest.broken assertion' duration='303' details='|r|n at Test.broken assertion(Test.groovy:32)|r|n' message='Condition not satisfied:|n|n|[(|'a|' * 523_999)|].first() == |'b|'|n || || |||n || || false|n || || Strings too large to calculate edit distance.|n || aaaaaaaaaaaaaaaaaaaa<...>actualFile='C:\Users\x\AppData\Local\Temp\actual1055937705826402150' actualIsTempFile='true']
]
java.text.ParseException: Incorrect property name.
Valid property list format is (name( )*=( )*'escaped_value'( )*)* where escape simbol is "|"
at jetbrains.buildServer.messages.serviceMessages.MapSerializerUtil.checkPropName(MapSerializerUtil.java:84)
at jetbrains.buildServer.messages.serviceMessages.MapSerializerUtil.stringToProperties(MapSerializerUtil.java:56)
at jetbrains.buildServer.messages.serviceMessages.ServiceMessage.parseAttributes(ServiceMessage.java:298)
at jetbrains.buildServer.messages.serviceMessages.ServiceMessage.init(ServiceMessage.java:423)
at jetbrains.buildServer.messages.serviceMessages.ServiceMessage.doParse(ServiceMessage.java:375)
at jetbrains.buildServer.messages.serviceMessages.ServiceMessage.parse(ServiceMessage.java:119)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter.processServiceMessages(OutputToGeneralTestEventsConverter.java:142)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter.processConsistentText(OutputToGeneralTestEventsConverter.java:99)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter$1.onLineAvailable(OutputToGeneralTestEventsConverter.java:53)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.a(OutputLineSplitter.java:158)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.b(OutputLineSplitter.java:111)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.a(OutputLineSplitter.java:80)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.process(OutputLineSplitter.java:53)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter.process(OutputToGeneralTestEventsConverter.java:71)
at com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil$2.onTextAvailable(SMTestRunnerConnectionUtil.java:213)
at sun.reflect.GeneratedMethodAccessor209.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.execution.process.ProcessHandler$5.invoke(ProcessHandler.java:239)
at com.sun.proxy.$Proxy21.onTextAvailable(Unknown Source)
at com.intellij.execution.process.ProcessHandler.notifyTextAvailable(ProcessHandler.java:213)
at com.intellij.execution.process.ColoredProcessHandler.textAvailable(ColoredProcessHandler.java:96)
at com.intellij.execution.process.ColoredProcessHandler.coloredTextAvailable(ColoredProcessHandler.java:71)
at com.intellij.execution.process.AnsiEscapeDecoder.processTextChunk(AnsiEscapeDecoder.java:267)
at com.intellij.execution.process.AnsiEscapeDecoder.escapeText(AnsiEscapeDecoder.java:67)
at com.intellij.execution.process.ColoredProcessHandler.notifyTextAvailable(ColoredProcessHandler.java:60)
at com.intellij.execution.process.BaseOSProcessHandler$SimpleOutputReader.onTextAvailable(BaseOSProcessHandler.java:295)
at com.intellij.util.io.BaseOutputReader.sendText(BaseOutputReader.java:202)
at com.intellij.util.io.BaseOutputReader.processInput(BaseOutputReader.java:186)
at com.intellij.util.io.BaseOutputReader.readAvailableNonBlocking(BaseOutputReader.java:105)
at com.intellij.util.io.BaseDataReader.readAvailable(BaseDataReader.java:85)
at com.intellij.util.io.BaseDataReader.doRun(BaseDataReader.java:163)
at com.intellij.util.io.BaseDataReader$1$1.run(BaseDataReader.java:66)
at com.intellij.util.ConcurrencyUtil.runUnderThreadName(ConcurrencyUtil.java:194)
at com.intellij.util.io.BaseDataReader$1.run(BaseDataReader.java:63)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
答案 0 :(得分:1)
最有可能是IntelliJ IDEA错误。您可以手动将以下依赖项添加到项目类路径中,以查看来自堆栈跟踪的反编译类:
{{IDEA root}}/lib/idea.jar
{{IDEA root}}/lib/serviceMessages.jar
您可以在下方找到堆栈跟踪顶部的checkPropName
和stringToProperties
方法:
@NotNull
public static Map<String, String> stringToProperties(@NotNull String string, @NotNull MapSerializerUtil.EscapeInfoProvider escaper, boolean strictNameCheck) throws ParseException {
String currentString = string;
LinkedHashMap result = new LinkedHashMap();
while(currentString.length() > 0) {
int nameSep = currentString.indexOf("=");
if (nameSep == -1) {
throw new ParseException("Property value not found\nValid property list format is (name( )*=( )*'escaped_value'( )*)* where escape simbol is \"|\"", 0);
}
String name = currentString.substring(0, nameSep).trim();
checkPropName(name, strictNameCheck);
currentString = currentString.substring(nameSep + 1).trim();
if (!currentString.startsWith("'")) {
throw new ParseException("Value should start with \"'\"\nValid property list format is (name( )*=( )*'escaped_value'( )*)* where escape simbol is \"|\"", 0);
}
currentString = currentString.substring(1);
int endOfValue = indexOf(currentString, '\'', escaper);
if (endOfValue < 0) {
throw new ParseException("Value should end with \"'\"\nValid property list format is (name( )*=( )*'escaped_value'( )*)* where escape simbol is \"|\"", 0);
}
String escapedValue = currentString.substring(0, endOfValue);
currentString = currentString.substring(endOfValue + 1).trim();
result.put(name, unescapeStr(escapedValue, escaper));
}
return result;
}
private static void checkPropName(String name, boolean strict) throws ParseException {
boolean isCorrect = strict ? isValidJavaIdentifier(name) : !hasSpaces(name);
if (!isCorrect) {
throw new ParseException("Incorrect property name.\nValid property list format is (name( )*=( )*'escaped_value'( )*)* where escape simbol is \"|\"", 0);
}
}
主要问题是stringToProperties
检索的输入字符串如下:
testFailed name='ListTessSpaceNexusVersionsTest.broken assertion' duration='303' details='|r|n at Test.broken assertion(Test.groovy:32)|r|n' message='Condition not satisfied:|n|n|[(|'a|' * 523_999)|].first() == |'b|'|n || || |||n || || false|n || || Strings too large to calculate edit distance.|n || aaaaaaaaaaaaaaaaaaaa<...>actualFile='C:\Users\x\AppData\Local\Temp\actual1055937705826402150' actualIsTempFile='true'
这是有问题的部分:
aaaaaaaaaaaaaaa<...>actualFile='C:\Users\x\AppData\Local\Temp\actual1055937705826402150' actualIsTempFile='true'
messages
属性无法正常转义。它应该在<...>
之后完成,因为actualFile='C:\...
定义了下一个属性。
aaaaaaaaaaaaaaa<...>123' actualFile='C:\Users\x\AppData\Local\Temp\actual1055937705826402150' actualIsTempFile='true'
如果IDEA切换消息字符串超过控制台的周期缓冲区大小(默认为1024 KB),会发生什么:
protected void processConsistentText(String text, final Key outputType, boolean tcLikeFakeOutput) {
final int cycleBufferSize = ConsoleBuffer.getCycleBufferSize();
if (USE_CYCLE_BUFFER && text.length() > cycleBufferSize) {
final StringBuilder builder = new StringBuilder(cycleBufferSize);
builder.append(text, 0, cycleBufferSize - 105);
builder.append("<...>");
builder.append(text, text.length() - 100, text.length());
text = builder.toString();
}
//....
//....
}
当我在IntelliJ IDEA中运行相同的测试时,控制台消息字符串以:
结束aaaaaaaaaaaaaaaaaaaaaaaaaaa<...>512
我怀疑在你的情况下会发生完全相同的事情,但不知何故传递给stringToProperties
方法的输入字符串会被破坏。如果我们查看堆栈跟踪,我们会发现在最终String传递给OutputLineSplitter
方法之前有stringToProperties
类。
at jetbrains.buildServer.messages.serviceMessages.ServiceMessage.parse(ServiceMessage.java:119)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter.processServiceMessages(OutputToGeneralTestEventsConverter.java:142)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter.processConsistentText(OutputToGeneralTestEventsConverter.java:99)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter$1.onLineAvailable(OutputToGeneralTestEventsConverter.java:53)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.a(OutputLineSplitter.java:158)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.b(OutputLineSplitter.java:111)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.a(OutputLineSplitter.java:80)
at com.intellij.execution.testframework.sm.runner.OutputLineSplitter.process(OutputLineSplitter.java:53)
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter.process(OutputToGeneralTestEventsConverter.java:71)
这个类的源代码可以在Github上找到:https://github.com/JetBrains/intellij-community/blob/master/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/OutputLineSplitter.java下面你也可以找到反编译版本:
package com.intellij.execution.testframework.sm.runner;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.util.Key;
import gnu.trove.THashMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
public abstract class OutputLineSplitter {
private static final String c = "##teamcity[";
public static final int TC_MESSAGE_LENGTH = "##teamcity[".length();
private final boolean d;
private final Map<Key, StringBuilder> a = new THashMap();
private final List<OutputLineSplitter.OutputChunk> b = new ArrayList();
public OutputLineSplitter(boolean var1) {
this.a.put(ProcessOutputTypes.SYSTEM, new StringBuilder());
this.a.put(ProcessOutputTypes.STDERR, new StringBuilder());
this.d = var1;
}
public void process(String var1, Key var2) {
int var3 = 0;
int var4 = 0;
boolean var5 = true;
for(int var6 = 0; var6 < var1.length(); ++var6) {
char var7 = var1.charAt(var6);
if (var7 == '\n') {
this.b(var1.substring(var3, var6 + 1), var2);
var3 = var6 + 1;
var5 = true;
} else if (!var5 && var7 == "##teamcity[".charAt(var4)) {
++var4;
if (var4 == TC_MESSAGE_LENGTH) {
int var8 = var6 + 1 - TC_MESSAGE_LENGTH;
this.b(var1.substring(var3, var8), var2);
this.flush();
var3 = var8;
var4 = 0;
}
} else {
var4 = var7 == "##teamcity[".charAt(0) ? 1 : 0;
var5 = false;
}
}
if (var3 < var1.length()) {
this.b(var1.substring(var3), var2);
}
}
private void b(String var1, Key var2) {
if (!this.a.keySet().contains(var2)) {
this.a(var1, var2);
} else {
StringBuilder var3 = (StringBuilder)this.a.get(var2);
if (!var1.endsWith("\n")) {
var3.append(var1);
return;
}
if (var3.length() > 0) {
var3.append(var1);
var1 = var3.toString();
var3.setLength(0);
}
this.onLineAvailable(var1, var2, false);
}
}
private void a(String var1, Key var2) {
int var3 = var1.length();
if (var3 != 0) {
List var4 = this.b;
synchronized(this.b) {
this.b.add(new OutputLineSplitter.OutputChunk(var2, var1));
}
char var7 = var1.charAt(var3 - 1);
if (var7 != '\n' && var7 != '\r') {
if (this.d && !this.isInTeamcityMessage()) {
this.a();
}
} else {
this.a();
}
}
}
private void a() {
ArrayList var1 = new ArrayList();
OutputLineSplitter.OutputChunk var2 = null;
List var3 = this.b;
Iterator var4;
OutputLineSplitter.OutputChunk var5;
synchronized(this.b) {
var4 = this.b.iterator();
while(true) {
if (!var4.hasNext()) {
this.b.clear();
break;
}
var5 = (OutputLineSplitter.OutputChunk)var4.next();
if (var2 != null && var5.getKey() == var2.getKey()) {
var2.append(var5.getText());
} else {
var2 = var5;
var1.add(var5);
}
}
}
boolean var8 = var1.size() == 1;
var4 = var1.iterator();
while(var4.hasNext()) {
var5 = (OutputLineSplitter.OutputChunk)var4.next();
this.onLineAvailable(var5.getText(), var5.getKey(), var8);
}
}
public void flush() {
this.a();
Iterator var1 = this.a.entrySet().iterator();
while(var1.hasNext()) {
Entry var2 = (Entry)var1.next();
StringBuilder var3 = (StringBuilder)var2.getValue();
if (var3.length() > 0) {
this.onLineAvailable(var3.toString(), (Key)var2.getKey(), false);
var3.setLength(0);
}
}
}
protected boolean isInTeamcityMessage() {
return this.b.stream().anyMatch((var0) -> {
return var0.getText().startsWith("##teamcity[");
});
}
protected abstract void onLineAvailable(@NotNull String var1, @NotNull Key var2, boolean var3);
private static class OutputChunk {
private final Key c;
private String a;
private StringBuilder b;
private OutputChunk(Key var1, String var2) {
this.c = var1;
this.a = var2;
}
public Key getKey() {
return this.c;
}
public String getText() {
if (this.b != null) {
this.a = this.b.toString();
this.b = null;
}
return this.a;
}
public void append(String var1) {
if (this.b == null) {
this.b = new StringBuilder(this.a);
this.a = null;
}
this.b.append(var1);
}
}
}
有趣的是,他们将新行字符检查硬编码与\n
进行比较,而不是使用System.lineSeparator()
方法,以便UNIX返回\n
和Windows \r\n
。我猜这是问题的根源。我使用IntelliJ IDEA Ultimate 2017.3.3和IntelliJ IDEA Community 2017.3在我的Linux机器上运行测试 - 它在两种情况下都按预期工作。解决问题的最佳方法是在调试模式下运行IntelliJ IDEA,将调试器连接到其JVM,设置断点,例如这里
at com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter.process(OutputToGeneralTestEventsConverter.java:71)
并查看字符串被破坏的位置。希望它有所帮助。