Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ include::include.adoc[]
* Fix `MockitoMockMaker` throws NPE on null object spockIssue:2337[]
* Fix `SourceToAstNodeAndSourceTranspiler` dropping nested generic type arguments, so signatures like `Iterator<Tuple2<Integer, Integer>>` are now rendered faithfully instead of `Iterator<Tuple2>` spockPull:2393[]
* Fix `SourceToAstNodeAndSourceTranspiler` rendering of several other constructs: the elvis operator, attribute access (`.@`), implicit-`it` closures, safe index access (`?[]`), numeric literal type suffixes (`L`, `F`, `D`, `G`), left-open ranges (`<..`), explicit type arguments of method calls, `for (index, value in iterable)` loops, multiple-assignment declarations on Groovy 5, and spurious empty `finally` blocks and `default` labels spockPull:2393[]
* Fix interaction mismatch rendering failing with a lexer error and dropping the similarity report when a mocked method or property name contains a `$` spockIssue:2364[]

=== Breaking Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.spockframework.mock.*;
import org.spockframework.runtime.Condition;
import org.spockframework.util.CollectionUtil;
import org.spockframework.util.TextUtil;

/**
*
Expand All @@ -43,7 +44,7 @@ public boolean isSatisfiedBy(IMockInvocation invocation) {
@Override
public String describeMismatch(IMockInvocation invocation) {
Condition condition = new Condition(CollectionUtil.listOf(invocation.getMethod().getName(), methodName, false),
String.format("methodName == \"%s\"", methodName), null, null,
String.format("methodName == \"%s\"", TextUtil.escapeGroovyDoubleQuotedString(methodName)), null, null,
null, null);
return condition.getRendering();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.spockframework.mock.IMockInvocation;
import org.spockframework.runtime.Condition;
import org.spockframework.util.CollectionUtil;
import org.spockframework.util.TextUtil;

/**
*
Expand All @@ -41,7 +42,7 @@ public boolean isSatisfiedBy(IMockInvocation invocation) {
@Override
public String describeMismatch(IMockInvocation invocation) {
Condition condition = new Condition(CollectionUtil.listOf(getPropertyName(invocation), propertyName, false),
String.format("propertyName == \"%s\"", propertyName), null, null,
String.format("propertyName == \"%s\"", TextUtil.escapeGroovyDoubleQuotedString(propertyName)), null, null,
null, null);
return condition.getRendering();
}
Expand Down
18 changes: 18 additions & 0 deletions spock-core/src/main/java/org/spockframework/util/TextUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,24 @@ public static String escape(CharSequence seq) {
return builder.toString();
}

/**
* Escapes {@code seq} for embedding in a Groovy double-quoted (GString) literal.
* On top of {@link #escape(char)}, this also escapes {@code "} and {@code $}, which
* would otherwise close the string or start an interpolation.
*/
public static String escapeGroovyDoubleQuotedString(CharSequence seq) {
StringBuilder builder = new StringBuilder(seq.length() * 3 / 2);
for (int i = 0; i < seq.length(); i++) {
char ch = seq.charAt(i);
if (ch == '"' || ch == '$') {
builder.append('\\').append(ch);
} else {
builder.append(escape(ch));
}
}
return builder.toString();
}

public static String printStackTrace(Throwable throwable) {
StringWriter writer = new StringWriter();
throwable.printStackTrace(new PrintWriter(writer));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package org.spockframework.smoke.mock
import org.hamcrest.CoreMatchers
import org.spockframework.EmbeddedSpecification
import org.spockframework.mock.TooFewInvocationsError
import spock.lang.Issue

import java.util.regex.Pattern

Expand Down Expand Up @@ -744,6 +745,76 @@ Unmatched invocations (ordered by similarity):
exceptionMessage == expected
}

@Issue("https://github.com/spockframework/spock/issues/2364")
def "can describe method name mismatch when the method name contains a dollar sign"() {
Comment thread
AndreasTu marked this conversation as resolved.
when:
runner.addClassImport(WithDollar)
runner.runFeatureBody("""
def mock = Mock(WithDollar)

when:
mock.compareTo('a')

then:
1 * mock.compareTo\$(_)
""")

then:
TooFewInvocationsError e = thrown()
def exceptionMessage = normalize(e.message)
def expected = normalize('''
Too few invocations for:

1 * mock.compareTo$(_) (0 invocations)

Unmatched invocations (ordered by similarity):

1 * mock.compareTo('a')
methodName == "compareTo\\$"
| |
compareTo false
1 difference (90% similarity)
compareTo(-)
compareTo($)
''')
exceptionMessage == expected
}

@Issue("https://github.com/spockframework/spock/issues/2364")
def "can describe property name mismatch when the property name contains a dollar sign"() {
Comment thread
AndreasTu marked this conversation as resolved.
when:
runner.addClassImport(WithDollar)
runner.runFeatureBody("""
def mock = Mock(WithDollar)

when:
mock.foo

then:
1 * mock.bar\$
""")

then:
TooFewInvocationsError e = thrown()
def exceptionMessage = normalize(e.message)
def expected = normalize('''
Too few invocations for:

1 * mock.bar$ (0 invocations)

Unmatched invocations (ordered by similarity):

1 * mock.getFoo()
propertyName == "bar\\$"
| |
foo false
4 differences (0% similarity)
(foo-)
(bar$)
''')
exceptionMessage == expected
}
Comment thread
Develop-KIM marked this conversation as resolved.

def "can describe regex method name mismatch"() {
when:
runner.addClassImport(Person)
Expand Down Expand Up @@ -1196,6 +1267,13 @@ One or more arguments(s) didn't match:
EMPTY_LINE.matcher(str.normalize().trim()).replaceAll('')
}

interface WithDollar {
int compareTo(Object o)
int compareTo$(Object o)
String getFoo()
String getBar$()
}

static class Person {
String name
int age
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,18 @@ class TextUtilSpec extends Specification {
"useTCPStacK" | "USE_TCP_STAC_K"
"fred123Usage" | "FRED123_USAGE"
}

def "escapeGroovyDoubleQuotedString"() {
expect:
TextUtil.escapeGroovyDoubleQuotedString(input) == escaped

where:
input | escaped
"" | ""
"foo" | "foo"
'compareTo$' | 'compareTo\\$'
'a"b' | 'a\\"b'
'a\\b' | 'a\\\\b'
'a\nb' | 'a\\nb'
}
}
Loading