spockframework/spock — pull request #49 by Fuud (2014-09-17 08:33:02+00:00)
Title: render exceptions in conditions as condition failure

## Idea

Spock has the best exception rendering I have ever see. For example, lets take a spec:
```groovy
def "responce code should starts with 200"(){
when:
def map = new HashMap()
map.put("responceCode", "404 NOT FOUND")
then:
map.get("responceCode").startsWith("200")
}
```

It fails and has good rendering:

```
Condition not satisfied:

map.get("responceCode").startsWith("200")
| | |
| 404 NOT FOUND false
[responceCode:404 NOT FOUND]

at org.spockframework.guice.JavaStubs.responce code should starts with 200(JavaStubs.groovy:98)
```

But how it will be rendered if map miss value for key "responceCode"? For example in this spec:
```groovy
def "responce code should starts with 200"(){
when:
def map = new HashMap()
map.put("ResponceCode", "404 NOT FOUND")
then:
map.get("responceCode").startsWith("200")
}
```

just an exception:
```
java.lang.NullPointerException: Cannot invoke method startsWith() on null object
at Test.responce code should starts with 200(Test.groovy:8)
```

we can understand that map does not contains entry for "responceCode" but we can not inspect what map really contains.

With my modifications it will become more friendly:
```
Condition not satisfied:

map.get("responceCode").startsWith("200")
| | |
| null java.lang.NullPointerException: Cannot invoke method startsWith() on null object
[ResponceCode:404 NOT FOUND]

at org.spockframework.guice.Test.responce code should starts with 200(Test.groovy:11)
Caused by: java.lang.NullPointerException: Cannot invoke method startsWith() on null object
... 1 more
```

So exceptions that occured during evaluating condition become part of rendering.

## Implementation
Lets take for example this condition:
```
map.get("key") == "abc"
```

Before my changes it will be transformed to (I omit unnecessary parts)
```
SpockRuntime.verifyCondition(
valueRecorder.reset(),
"map.get(\"key\") == \"abc\"",
8,
9,
null,
valueRecorder.record(
6,
Boolean.valueOf(
ScriptBytecodeAdapter.compareEqual(
valueRecorder.record(4,
ScriptBytecodeAdapter.invokeMethodN(
Test.class,
valueRecorder.record(0, map),
(String)valueRecorder.record(1, "get"),
[valueRecorder.record(2, "key") ]
)
),
valueRecorder.record(5, "abc")
)
)
)
);
```

We need to:
1) handle exception, so surround this expression with try-catch.
2) to understand number of value that was not recorded because of exception, so lets remember value number before recording value (and before evaluating expression for this value).
```groovy
try {
SpockRuntime.verifyCondition(
$spock_valueRecorder.reset(),
"map.get(\"key\") == \"abc\"",
14,
7,
null,
$spock_valueRecorder.record(
$spock_valueRecorder.startRecordingValue(6),
Boolean.valueOf(
ScriptBytecodeAdapter.compareEqual(
$spock_valueRecorder.record(
$spock_valueRecorder.startRecordingValue(4),
ScriptBytecodeAdapter.invokeMethodN(
ExceptionsInConditionsSpec.class,
$spock_valueRecorder.record(
$spock_valueRecorder.startRecordingValue(0),
map
),
(String) ShortTypeHandling.castToString(
$spock_valueRecorder.record(
$spock_valueRecorder.startRecordingValue(1),
"get")
),
[$spock_valueRecorder.record(
$spock_valueRecorder.startRecordingValue(2),
"key"
)]
)
),
$spock_valueRecorder.record(
$spock_valueRecorder.startRecordingValue(5),
"abc")
)
)
)
);
}
catch (Throwable throwable) {
SpockRuntime.conditionFailedWithException($spock_valueRecorder, "map.get(\"key\") == \"abc\"", 14, 7, null, throwable);
}
```

ValueRecorder,startRecording method:
```groovy
private final Stack startedRecordings = new Stack();

public int startRecordingValue(int index){
startedRecordings.push(index);
return index;
}

```

So we add value number to stack before evaluating expression for this value. And remove value number from top of stack when value is written to ValueRecording. And when exception is thrown we can learn where exception is thrown by peeking value from top of stack.