Skip to content

Commit a65ea49

Browse files
author
Ronald Holshausen
committed
Small refactor in preparation of better diff output #370
1 parent 67a9587 commit a65ea49

File tree

8 files changed

+174
-78
lines changed

8 files changed

+174
-78
lines changed

pact-jvm-consumer/src/main/scala/au/com/dius/pact/consumer/PrettyPrinter.scala

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import au.com.dius.pact.model.{RequestResponseInteraction, _}
44
import difflib.DiffUtils
55
import groovy.json.JsonOutput
66

7-
import scala.Some
8-
97
object PrettyPrinter {
108
//TODO: allow configurable context lines
119
val defaultContextLines = 3
@@ -44,7 +42,7 @@ object PrettyPrinter {
4442
def printProblem(interaction:Interaction, partial: Seq[RequestPartMismatch]): String = {
4543
partial.flatMap {
4644
case HeaderMismatch(key, expected, actual, mismatch) => printStringMismatch("Header " + key, expected, actual)
47-
case BodyMismatch(expected, actual, mismatch, path) => printStringMismatch("Body",
45+
case BodyMismatch(expected, actual, mismatch, path, _) => printStringMismatch("Body",
4846
JsonOutput.prettyPrint(expected.toString), JsonOutput.prettyPrint(actual.toString))
4947
case CookieMismatch(expected, actual) => printDiff("Cookies", expected.sorted, actual.sorted)
5048
case PathMismatch(expected, actual, _) => printDiff("Path", List(expected), List(actual), 0)

pact-jvm-consumer/src/test/groovy/au/com/dius/pact/consumer/PrettyPrinterSpec.groovy

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class PrettyPrinterSpec extends Specification {
4343

4444
def 'body mismatch'() {
4545
expect:
46-
print(new BodyMismatch('{"foo": "bar"}', '{"ork": "Bif"}', Option.empty(), '/')) ==
46+
print(new BodyMismatch('{"foo": "bar"}', '{"ork": "Bif"}', Option.empty(), '/', Option.empty())) ==
4747
"""--- Body
4848
|$plus
4949
|@@ -1,3 +1,3 @@

pact-jvm-matchers/src/main/scala/au/com/dius/pact/model/Matching.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ case class HeaderMismatch(headerKey: String, expected: String, actual: String, m
4141
}
4242
}
4343
case class BodyTypeMismatch(expected: String, actual: String) extends RequestPartMismatch with ResponsePartMismatch
44-
case class BodyMismatch(expected: Any, actual: Any, mismatch: Option[String] = None, path: String = "/")
44+
case class BodyMismatch(expected: Any, actual: Any, mismatch: Option[String] = None, path: String = "/", diff: Option[String] = None)
4545
extends RequestPartMismatch with ResponsePartMismatch {
4646
override def description: String = mismatch match {
4747
case Some(message) => s"BodyMismatch - $message"

pact-jvm-provider-gradle/src/test/groovy/au/com/dius/pact/provider/gradle/ResponseComparisonTest.groovy

+3-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ class ResponseComparisonTest {
8282
void 'comparing bodies should show all the differences'() {
8383
actualBody = '{"stuff": "should make the test fail"}'
8484
def result = testSubject().body
85-
assert result.comparison == ['$.body.stuff': "Expected 'is good' but received 'should make the test fail'"]
85+
assert result.comparison == [
86+
'$.body.stuff': [mismatch: "Expected 'is good' but received 'should make the test fail'", diff: '']
87+
]
8688
assert result.diff[0] == '@1'
8789
assert result.diff[1] == '- "stuff": "is good"'
8890
assert result.diff[2] == '+ "stuff": "should make the test fail"'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package au.com.dius.pact.provider
2+
3+
import au.com.dius.pact.model.OptionalBody
4+
import difflib.Delta
5+
import difflib.Patch
6+
import groovy.json.JsonOutput
7+
import groovy.json.JsonSlurper
8+
9+
/**
10+
* Utility methods for generating diffs
11+
*/
12+
class DiffUtils {
13+
14+
private static final String NL = '\n'
15+
16+
static generateDiff(String expectedBodyString, String actualBodyString) {
17+
def expectedLines = expectedBodyString.split(NL) as List
18+
def actualLines = actualBodyString.split(NL) as List
19+
Patch<String> patch = difflib.DiffUtils.diff(expectedLines, actualLines)
20+
21+
def diff = []
22+
23+
patch.deltas.each { Delta<String> delta ->
24+
diff << "@${delta.original.position}"
25+
if (delta.original.position > 1) {
26+
diff << expectedLines[delta.original.position - 1]
27+
}
28+
29+
delta.original.lines.each {
30+
diff << "-$it"
31+
}
32+
delta.revised.lines.each {
33+
diff << "+$it"
34+
}
35+
36+
int pos = delta.original.position + delta.original.lines.size()
37+
if (pos < expectedLines.size()) {
38+
diff << expectedLines[pos]
39+
}
40+
41+
diff << ''
42+
}
43+
diff
44+
}
45+
46+
static generateMismatchDiff(String pathString, String actual, OptionalBody expected) {
47+
def path = pathString.split('\\.').drop(2) as List
48+
def actualBody = ''
49+
if (actual) {
50+
actualBody = walkGraph(new JsonSlurper().parseText(actual), path)
51+
}
52+
53+
def expectedBody = ''
54+
if (expected?.present) {
55+
expectedBody = walkGraph(new JsonSlurper().parseText(expected.value), path)
56+
}
57+
58+
generateDiff(expectedBody, actualBody)
59+
}
60+
61+
private static String walkGraph(data, List<String> path) {
62+
def filteredData = walk(data, path)
63+
if (filteredData) {
64+
JsonOutput.prettyPrint(JsonOutput.toJson(filteredData))
65+
} else {
66+
''
67+
}
68+
}
69+
70+
private static walk(data, List<String> path) {
71+
if (path) {
72+
if (data instanceof Map) {
73+
walk(data[path.first()], path.drop(1))
74+
} else if (data instanceof List) {
75+
def p = path.first()
76+
if (p.matches('\\d+')) {
77+
walk(data[Integer.parseInt(p)], path.drop(1))
78+
} else {
79+
null
80+
}
81+
} else {
82+
null
83+
}
84+
} else {
85+
data
86+
}
87+
}
88+
}

pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/ProviderVerifier.groovy

+7-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class ProviderVerifier {
2626
static final String PACT_FILTER_DESCRIPTION = 'pact.filter.description'
2727
static final String PACT_FILTER_PROVIDERSTATE = 'pact.filter.providerState'
2828
static final String PACT_SHOW_STACKTRACE = 'pact.showStacktrace'
29+
static final String PACT_SHOW_FULLDIFF = 'pact.showFullDiff'
2930

3031
def projectHasProperty = { }
3132
def projectGetProperty = { }
@@ -50,7 +51,12 @@ class ProviderVerifier {
5051
}
5152

5253
void initialiseReporters(ProviderInfo provider) {
53-
reporters.each { it.initialise(provider) }
54+
reporters.each {
55+
if (it.hasProperty('displayFullDiff')) {
56+
it.displayFullDiff = callProjectHasProperty(PACT_SHOW_FULLDIFF)
57+
}
58+
it.initialise(provider)
59+
}
5460
}
5561

5662
void runVerificationForConsumer(Map failures, ProviderInfo provider, ConsumerInfo consumer) {

pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/ResponseComparison.groovy

+31-54
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ import au.com.dius.pact.model.ResponseMatching$
1212
import au.com.dius.pact.model.ResponsePartMismatch
1313
import au.com.dius.pact.model.StatusMismatch
1414
import au.com.dius.pact.model.v3.messaging.Message
15-
import difflib.Delta
16-
import difflib.DiffUtils
17-
import difflib.Patch
18-
import groovy.json.JsonBuilder
1915
import groovy.json.JsonOutput
2016
import groovy.json.JsonSlurper
2117
import org.apache.commons.lang3.StringUtils
@@ -28,9 +24,7 @@ import scala.collection.JavaConverters$
2824
*/
2925
class ResponseComparison {
3026

31-
private static final String NL = '\n'
32-
33-
Response expected
27+
Response expected
3428
Map actual
3529
int actualStatus
3630
Map actualHeaders
@@ -118,60 +112,43 @@ class ResponseComparison {
118112
"type was '${bodyTypeMismatch.actual()}'"]
119113
} else if (mismatches.any { it instanceof BodyMismatch }) {
120114
result.comparison = mismatches.findAll { it instanceof BodyMismatch }.collectEntries {
121-
BodyMismatch bodyMismatch -> [bodyMismatch.path(), bodyMismatch.mismatch().defined ?
122-
bodyMismatch.mismatch().get() : 'mismatch']
123-
}
124-
125-
String actualBodyString = ''
126-
if (actualBody) {
127-
if (actual.contentType.mimeType ==~ 'application/.*json') {
128-
def bodyMap = new JsonSlurper().parseText(actualBody)
129-
def bodyJson = JsonOutput.toJson(bodyMap)
130-
actualBodyString = JsonOutput.prettyPrint(bodyJson)
131-
} else {
132-
actualBodyString = actualBody
133-
}
134-
}
135-
136-
String expectedBodyString = ''
137-
if (expected.body.present) {
138-
if (expected.jsonBody()) {
139-
expectedBodyString = JsonOutput.prettyPrint(expected.body.value)
140-
} else {
141-
expectedBodyString = expected.body.value
142-
}
115+
BodyMismatch bodyMismatch -> [
116+
bodyMismatch.path(), [
117+
mismatch: bodyMismatch.mismatch().defined ? bodyMismatch.mismatch().get() : 'mismatch',
118+
diff: bodyMismatch.diff().defined ? bodyMismatch.diff().get() : ''
119+
]
120+
]
143121
}
144122

145-
def expectedLines = expectedBodyString.split(NL) as List
146-
def actualLines = actualBodyString.split(NL) as List
147-
Patch<String> patch = DiffUtils.diff(expectedLines, actualLines)
148-
149-
def diff = []
150-
151-
patch.deltas.each { Delta<String> delta ->
152-
diff << "@${delta.original.position}"
153-
if (delta.original.position > 1) {
154-
diff << expectedLines[delta.original.position - 1]
155-
}
156-
157-
delta.original.lines.each {
158-
diff << "-$it"
159-
}
160-
delta.revised.lines.each {
161-
diff << "+$it"
162-
}
123+
result.diff = generateFullDiff(actualBody, this.actual.contentType.mimeType as String,
124+
expected.body.present ? expected.body.value : '', expected.jsonBody())
125+
}
163126

164-
int pos = delta.original.position + delta.original.lines.size()
165-
if (pos < expectedLines.size()) {
166-
diff << expectedLines[pos]
167-
}
127+
result
128+
}
168129

169-
diff << ''
130+
private static generateFullDiff(String actual, String mimeType, String response, Boolean jsonBody) {
131+
String actualBodyString = ''
132+
if (actual) {
133+
if (mimeType ==~ 'application/.*json') {
134+
def bodyMap = new JsonSlurper().parseText(actual)
135+
def bodyJson = JsonOutput.toJson(bodyMap)
136+
actualBodyString = JsonOutput.prettyPrint(bodyJson)
137+
} else {
138+
actualBodyString = actual
170139
}
140+
}
171141

172-
result.diff = diff
142+
String expectedBodyString = ''
143+
if (response) {
144+
if (jsonBody) {
145+
expectedBodyString = JsonOutput.prettyPrint(response)
146+
} else {
147+
expectedBodyString = response
148+
}
173149
}
174150

175-
result
151+
DiffUtils.generateDiff(expectedBodyString, actualBodyString)
176152
}
153+
177154
}

pact-jvm-provider/src/main/groovy/au/com/dius/pact/provider/reporters/AnsiConsoleReporter.groovy

+42-17
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import au.com.dius.pact.provider.org.fusesource.jansi.AnsiConsole
1313
@SuppressWarnings(['DuplicateStringLiteral', 'MethodCount'])
1414
class AnsiConsoleReporter implements VerifierReporter {
1515

16+
boolean displayFullDiff = false
17+
1618
@Override
1719
void setReportDir(File reportDir) { }
1820

@@ -185,7 +187,8 @@ class AnsiConsoleReporter implements VerifierReporter {
185187
AnsiConsole.out().println("$i) ${err.key}")
186188
if (err.value instanceof Throwable) {
187189
displayError(err.value)
188-
} else if (err.value instanceof Map && err.value.containsKey('diff')) {
190+
} else if (err.value instanceof Map && err.value.containsKey('comparison') &&
191+
err.value.comparison instanceof Map) {
189192
displayDiff(err)
190193
} else if (err.value instanceof String) {
191194
AnsiConsole.out().println(" ${err.value}")
@@ -200,25 +203,47 @@ class AnsiConsoleReporter implements VerifierReporter {
200203
}
201204
}
202205

203-
static void displayDiff(err) {
204-
err.value.comparison.each { key, message ->
205-
AnsiConsole.out().println(" $key -> $message")
206+
@SuppressWarnings('AbcMetric')
207+
void displayDiff(err) {
208+
err.value.comparison.each { key, messageAndDiff ->
209+
AnsiConsole.out().println(" $key -> $messageAndDiff.mismatch")
210+
AnsiConsole.out().println()
211+
212+
if (messageAndDiff.diff) {
213+
AnsiConsole.out().println(' Diff:')
214+
AnsiConsole.out().println()
215+
216+
messageAndDiff.diff.each { delta ->
217+
if (delta.startsWith('@')) {
218+
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.CYAN).a(delta).reset())
219+
} else if (delta.startsWith('-')) {
220+
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.RED).a(delta).reset())
221+
} else if (delta.startsWith('+')) {
222+
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.GREEN).a(delta).reset())
223+
} else {
224+
AnsiConsole.out().println(" $delta")
225+
}
226+
}
227+
AnsiConsole.out().println()
228+
}
206229
}
207230

208-
AnsiConsole.out().println()
209-
AnsiConsole.out().println(' Diff:')
210-
AnsiConsole.out().println()
211-
212-
err.value.diff.each { delta ->
213-
if (delta.startsWith('@')) {
214-
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.CYAN).a(delta).reset())
215-
} else if (delta.startsWith('-')) {
216-
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.RED).a(delta).reset())
217-
} else if (delta.startsWith('+')) {
218-
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.GREEN).a(delta).reset())
219-
} else {
220-
AnsiConsole.out().println(" $delta")
231+
if (displayFullDiff) {
232+
AnsiConsole.out().println(' Full Diff:')
233+
AnsiConsole.out().println()
234+
235+
err.value.diff.each { delta ->
236+
if (delta.startsWith('@')) {
237+
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.CYAN).a(delta).reset())
238+
} else if (delta.startsWith('-')) {
239+
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.RED).a(delta).reset())
240+
} else if (delta.startsWith('+')) {
241+
AnsiConsole.out().println(Ansi.ansi().a(' ').fg(Ansi.Color.GREEN).a(delta).reset())
242+
} else {
243+
AnsiConsole.out().println(" $delta")
244+
}
221245
}
246+
AnsiConsole.out().println()
222247
}
223248
}
224249

0 commit comments

Comments
 (0)