当通过IntelliJ IDEA运行时,Spock中带有lenghty元素的列表的断言不会失败

时间:2018-01-30 09:38:17

标签: intellij-idea groovy spock assertions

以下测试总是在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)

1 个答案:

答案 0 :(得分:1)

最有可能是IntelliJ IDEA错误。您可以手动将以下依赖项添加到项目类路径中,以查看来自堆栈跟踪的反编译类:

  • {{IDEA root}}/lib/idea.jar
  • {{IDEA root}}/lib/serviceMessages.jar

您可以在下方找到堆栈跟踪顶部的checkPropNamestringToProperties方法:

@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)

并查看字符串被破坏的位置。希望它有所帮助。