Skip to content

Commit c8a3a13

Browse files
authored
Merge pull request #119 from radarsh/feature/show-full-stacktraces
Show full stacktraces
2 parents a4d121c + 52fbd06 commit c8a3a13

15 files changed

+373
-56
lines changed

README.md

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ The following shows the complete default configuration applied when you configur
5757
testlogger {
5858
theme 'standard'
5959
showExceptions true
60+
showStackTraces true
61+
showFullStackTraces false
62+
showCauses true
6063
slowThreshold 2000
6164
showSummary true
6265
showSimpleNames false
@@ -224,6 +227,35 @@ testlogger {
224227
By default all the above three flags are turned on. If you have chosen to display standard streams by setting
225228
`showStandardStreams` flag to `true`, any output produced by filtered out tests will not be displayed.
226229

230+
### Relationship between `testlogger` and `Test.testLogging`
231+
232+
Where possible, the plugin's `testlogger` extension tries to react to equivalent properties of Gradle's `Test.testLogging`
233+
extension. However, if a value is explicitly configured under the `testlogger` extension, the plugin __does not__ react to the
234+
corresponding property of `Test.testLogging`. The below table demonstrates this in more detail.
235+
236+
| Property | `Test.testLogging` value | `testlogging` value | Effective value |
237+
|---------------------------|---------------------------------------|------------------------|---------------- |
238+
| `showStandardStreams` | `true` | not configured | `true` |
239+
| `showStandardStreams` | `true` | `false` | `false` |
240+
| `showStandardStreams` | `false` | `true` | `true` |
241+
| `showExceptions` | `true` | not configured | `true` |
242+
| `showExceptions` | `true` | `false` | `false` |
243+
| `showExceptions` | `false` | `true` | `true` |
244+
| `showStackTraces` | `true` | not configured | `true` |
245+
| `showStackTraces` | `true` | `false` | `false` |
246+
| `showStackTraces` | `false` | `true` | `true` |
247+
| `showFullStackTraces` | `testLogging.exceptionFormat = FULL` | not configured | `true` |
248+
| `showFullStackTraces` | `testLogging.exceptionFormat = SHORT` | not configured | `false` |
249+
| `showFullStackTraces` | `testLogging.exceptionFormat = FULL` | `false` | `false` |
250+
| `showFullStackTraces` | `testLogging.exceptionFormat = SHORT` | `true` | `true` |
251+
| `showCauses` | `true` | not configured | `true` |
252+
| `showCauses` | `true` | `false` | `false` |
253+
| `showCauses` | `false` | `true` | `true` |
254+
255+
In other words, an explicitly configured `testlogger` property, despite it being `false`, takes precedence over any
256+
value of `Test.testLogging`.
257+
258+
227259
## FAQ
228260

229261
### Does it work on Windows?
@@ -245,15 +277,6 @@ Yes. You will need to switch to a suitable parallel theme though. This can be on
245277
[`maxParallelForks`](https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:maxParallelForks)
246278
greater than 1. They achieve this by sacrificing the ability to group tests and thus some readability is lost.
247279

248-
### How are `testlogger` and `Test.testLogging` related?
249-
250-
Until recently, they were unrelated. While this plugin's `testlogger` has many properties named identical to the ones in Gradle's
251-
`Test.testLogging`, to a large extent, they are kept isolated by design.
252-
253-
However, as of this writing `testlogger.showStandardStreams` property has been made to react to `testLogging.showStandardStreams`
254-
property as long as one doesn't configure a value for `testlogger.showStandardStreams`. If a value is configured for
255-
`testlogger.showStandardStreams` (even if it is `false`), the plugin ignores `testLogging.showStandardStreams` altogether.
256-
257280
### Can this plugin co-exist with junit-platform-gradle-plugin?
258281

259282
Due to certain unknown reasons, `junit-platform-gradle-plugin` is incompatible with `gradle-test-logger-plugin`. If you are still

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ test {
8282
testClassesDirs += sourceSets.functionalTest.output.classesDirs
8383
classpath += sourceSets.functionalTest.runtimeClasspath
8484
systemProperty 'file.encoding', 'UTF-8'
85+
86+
// testLogging {
87+
// exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
88+
// }
8589

8690
testlogger {
8791
theme 'mocha'

src/main/groovy/com/adarshr/gradle/testlogger/TestLoggerExtension.groovy

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import org.gradle.api.tasks.testing.logging.TestLogging
99
import static com.adarshr.gradle.testlogger.theme.ThemeType.PLAIN
1010
import static com.adarshr.gradle.testlogger.theme.ThemeType.STANDARD
1111
import static org.gradle.api.logging.configuration.ConsoleOutput.Plain
12+
import static org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
1213

1314
@CompileStatic
1415
@SuppressWarnings("GroovyUnusedDeclaration")
1516
class TestLoggerExtension {
1617

1718
ThemeType theme = STANDARD
1819
boolean showExceptions = true
20+
boolean showCauses = true
21+
boolean showStackTraces = true
22+
boolean showFullStackTraces = false
1923
long slowThreshold = 2000
2024
boolean showSummary = true
2125
boolean showStandardStreams = false
@@ -38,6 +42,9 @@ class TestLoggerExtension {
3842
private TestLoggerExtension(TestLoggerExtension source) {
3943
this.theme = source.theme
4044
this.showExceptions = source.showExceptions
45+
this.showCauses = source.showCauses
46+
this.showStackTraces = source.showStackTraces
47+
this.showFullStackTraces = source.showFullStackTraces
4148
this.slowThreshold = source.slowThreshold
4249
this.showSummary = source.showSummary
4350
this.showStandardStreams = source.showStandardStreams
@@ -75,6 +82,21 @@ class TestLoggerExtension {
7582
this.configuredProperties << 'showExceptions'
7683
}
7784

85+
void setShowCauses(boolean showCauses) {
86+
this.showCauses = showCauses
87+
this.configuredProperties << 'showCauses'
88+
}
89+
90+
void setShowStackTraces(boolean showStackTraces) {
91+
this.showStackTraces = showStackTraces
92+
this.configuredProperties << 'showStackTraces'
93+
}
94+
95+
void setShowFullStackTraces(boolean showFullStackTraces) {
96+
this.showFullStackTraces = showFullStackTraces
97+
this.configuredProperties << 'showFullStackTraces'
98+
}
99+
78100
void setSlowThreshold(long slowThreshold) {
79101
this.slowThreshold = slowThreshold
80102
this.configuredProperties << 'slowThreshold'
@@ -134,6 +156,22 @@ class TestLoggerExtension {
134156
this.showStandardStreams = testLogging.showStandardStreams
135157
this.configuredProperties -= 'showStandardStreams'
136158
}
159+
if (!this.configuredProperties.contains('showExceptions')) {
160+
this.showExceptions = testLogging.showExceptions
161+
this.configuredProperties -= 'showExceptions'
162+
}
163+
if (!this.configuredProperties.contains('showCauses')) {
164+
this.showCauses = testLogging.showCauses
165+
this.configuredProperties -= 'showCauses'
166+
}
167+
if (!this.configuredProperties.contains('showStackTraces')) {
168+
this.showStackTraces = testLogging.showStackTraces
169+
this.configuredProperties -= 'showStackTraces'
170+
}
171+
if (!this.configuredProperties.contains('showFullStackTraces')) {
172+
this.showFullStackTraces = testLogging.showStackTraces && testLogging.exceptionFormat == FULL
173+
this.configuredProperties -= 'showFullStackTraces'
174+
}
137175

138176
this
139177
}
@@ -153,6 +191,9 @@ class TestLoggerExtension {
153191
TestLoggerExtension applyOverrides(Map<String, String> overrides) {
154192
override(overrides, 'theme', ThemeType)
155193
override(overrides, 'showExceptions', Boolean)
194+
override(overrides, 'showCauses', Boolean)
195+
override(overrides, 'showStackTraces', Boolean)
196+
override(overrides, 'showFullStackTraces', Boolean)
156197
override(overrides, 'slowThreshold', Long)
157198
override(overrides, 'showSummary', Boolean)
158199
override(overrides, 'showStandardStreams', Boolean)

src/main/groovy/com/adarshr/gradle/testlogger/theme/AbstractTheme.groovy

Lines changed: 96 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,18 @@ import com.adarshr.gradle.testlogger.TestLoggerExtension
55
import com.adarshr.gradle.testlogger.TestResultWrapper
66
import groovy.transform.CompileStatic
77

8-
import static com.adarshr.gradle.testlogger.util.RendererUtils.escape
8+
import static com.adarshr.gradle.testlogger.util.RendererUtils.preserveAnsi
99
import static java.lang.System.lineSeparator
1010

11-
@SuppressWarnings("GrMethodMayBeStatic")
1211
@CompileStatic
1312
abstract class AbstractTheme implements Theme {
1413

1514
final ThemeType type
16-
17-
protected final boolean showExceptions
18-
protected final long slowThreshold
19-
protected final boolean showSummary
20-
protected final boolean showStandardStreams
21-
protected final boolean showPassed
22-
protected final boolean showSkipped
23-
protected final boolean showFailed
15+
protected final TestLoggerExtension extension
2416

2517
AbstractTheme(TestLoggerExtension extension) {
2618
this.type = extension.theme
27-
this.showExceptions = extension.showExceptions
28-
this.slowThreshold = extension.slowThreshold
29-
this.showSummary = extension.showSummary
30-
this.showStandardStreams = extension.showStandardStreams
31-
this.showPassed = extension.showPassed
32-
this.showSkipped = extension.showSkipped
33-
this.showFailed = extension.showFailed
19+
this.extension = extension
3420
}
3521

3622
@Override
@@ -69,26 +55,107 @@ abstract class AbstractTheme implements Theme {
6955
protected String exceptionText(TestDescriptorWrapper descriptor, TestResultWrapper result, int indent) {
7056
def line = new StringBuilder()
7157

72-
if (showExceptions) {
73-
def indentation = ' ' * indent
58+
if (!extension.showExceptions) {
59+
return line
60+
}
61+
62+
line << "${lineSeparator()}${lineSeparator()}"
63+
64+
new StackTracePrinter(descriptor, ' ' * indent, line)
65+
.printStackTrace(result.exception)
66+
.toString()
67+
}
68+
69+
private class StackTracePrinter {
70+
final TestDescriptorWrapper descriptor
71+
final String indentation
72+
final StringBuilder line
7473

75-
line << "${lineSeparator()}${lineSeparator()}"
74+
StackTracePrinter(TestDescriptorWrapper descriptor, String indentation, StringBuilder line) {
75+
this.descriptor = descriptor
76+
this.indentation = indentation
77+
this.line = line
78+
}
7679

77-
line << result.exception.toString().trim().readLines().collect {
78-
"${indentation}${escape(it)}"
79-
}.join(lineSeparator())
80+
StackTracePrinter printStackTrace(Throwable exception, List<StackTraceElement> parentStackTrace = [], boolean cause = false) {
81+
if (cause) {
82+
line << "${indentation}Caused by: "
83+
}
8084

85+
line << message(exception, cause)
8186
line << lineSeparator()
8287

83-
line << result.exception.stackTrace.find {
84-
it.className == descriptor.className
85-
}.collect {
86-
"${indentation} at ${escape(it.toString())}"
87-
}.join(lineSeparator())
88+
if (!extension.showStackTraces) {
89+
return this
90+
}
91+
92+
def filteredTrace = filter(exception.stackTrace)
8893

94+
line << stackTrace(filteredTrace, countCommonFrames(parentStackTrace, filteredTrace))
8995
line << lineSeparator()
96+
97+
if (extension.showCauses && exception.cause) {
98+
printStackTrace(exception.cause, filteredTrace, true)
99+
}
100+
101+
this
102+
}
103+
104+
String message(Throwable exception, boolean cause) {
105+
exception.toString()
106+
.trim()
107+
.readLines()
108+
.withIndex()
109+
.collect { String message, index ->
110+
"${index != 0 || !cause ? indentation : ''}${preserveAnsi(message)}"
111+
}.join(lineSeparator())
112+
}
113+
114+
String stackTrace(List<StackTraceElement> stackTrace, int commonFrames) {
115+
def trace = new StringBuilder(stackTrace
116+
.subList(0, stackTrace.size() - commonFrames)
117+
.collect {
118+
"${indentation} at ${preserveAnsi(it.toString())}"
119+
}.join(lineSeparator()))
120+
121+
if (commonFrames) {
122+
trace << "${lineSeparator()}${indentation} ... ${commonFrames} more"
123+
}
124+
125+
trace.toString()
90126
}
91127

92-
line
128+
int countCommonFrames(List<StackTraceElement> parentStackTrace, List<StackTraceElement> stackTrace) {
129+
int count = 0
130+
131+
if (parentStackTrace.empty) {
132+
return count;
133+
}
134+
135+
int i = stackTrace.size() - 1, j = parentStackTrace.size() - 1
136+
137+
while (i >= 1 && j >= 0 && stackTrace[i] == parentStackTrace[j]) {
138+
++count; i--; j--
139+
}
140+
141+
count
142+
}
143+
144+
List<StackTraceElement> filter(StackTraceElement[] stackTrace) {
145+
if (extension.showFullStackTraces) {
146+
return stackTrace.toList()
147+
}
148+
149+
stackTrace.find {
150+
it.className == descriptor.className
151+
}.collect {
152+
it as StackTraceElement
153+
}
154+
}
155+
156+
@Override
157+
String toString() {
158+
line.toString()
159+
}
93160
}
94161
}

src/main/groovy/com/adarshr/gradle/testlogger/theme/MochaTheme.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class MochaTheme extends AbstractTheme {
8383
}
8484

8585
protected String summaryText(TestDescriptorWrapper descriptor, TestResultWrapper result, int indent) {
86-
if (!showSummary) {
86+
if (!extension.showSummary) {
8787
return ''
8888
}
8989

@@ -113,7 +113,7 @@ class MochaTheme extends AbstractTheme {
113113
}
114114

115115
protected String standardStreamTextInternal(String lines, int indent) {
116-
if (!showStandardStreams || !lines) {
116+
if (!extension.showStandardStreams || !lines) {
117117
return ''
118118
}
119119

src/main/groovy/com/adarshr/gradle/testlogger/theme/PlainTheme.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class PlainTheme extends AbstractTheme {
4545

4646
@Override
4747
String summaryText(TestDescriptorWrapper descriptor, TestResultWrapper result) {
48-
if (!showSummary) {
48+
if (!extension.showSummary) {
4949
return ''
5050
}
5151

@@ -88,7 +88,7 @@ class PlainTheme extends AbstractTheme {
8888
}
8989

9090
protected String standardStreamTextInternal(String lines, int indent) {
91-
if (!showStandardStreams || !lines) {
91+
if (!extension.showStandardStreams || !lines) {
9292
return ''
9393
}
9494

src/main/groovy/com/adarshr/gradle/testlogger/theme/StandardTheme.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class StandardTheme extends AbstractTheme {
6161

6262
@Override
6363
String summaryText(TestDescriptorWrapper descriptor, TestResultWrapper result) {
64-
if (!showSummary) {
64+
if (!extension.showSummary) {
6565
return ''
6666
}
6767

@@ -105,7 +105,7 @@ class StandardTheme extends AbstractTheme {
105105
}
106106

107107
protected String standardStreamTextInternal(String lines, int indent) {
108-
if (!showStandardStreams || !lines) {
108+
if (!extension.showStandardStreams || !lines) {
109109
return ''
110110
}
111111

0 commit comments

Comments
 (0)