Skip to content

Commit e3ad6b5

Browse files
committed
Merge branch '2.4.x'
Closes gh-25407
2 parents 9836b06 + 29bbbc3 commit e3ad6b5

File tree

3 files changed

+109
-77
lines changed

3 files changed

+109
-77
lines changed
Lines changed: 12 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,14 +17,11 @@
1717
package org.springframework.boot.build.testing;
1818

1919
import java.util.ArrayList;
20-
import java.util.Collections;
2120
import java.util.List;
22-
import java.util.Map;
23-
import java.util.TreeMap;
2421

25-
import org.gradle.BuildResult;
2622
import org.gradle.api.Plugin;
2723
import org.gradle.api.Project;
24+
import org.gradle.api.provider.Provider;
2825
import org.gradle.api.tasks.testing.Test;
2926
import org.gradle.api.tasks.testing.TestDescriptor;
3027
import org.gradle.api.tasks.testing.TestListener;
@@ -39,46 +36,37 @@ public class TestFailuresPlugin implements Plugin<Project> {
3936

4037
@Override
4138
public void apply(Project project) {
42-
TestResultsExtension testResults = getOrCreateTestResults(project);
39+
Provider<TestResultsOverview> testResultsOverview = project.getGradle().getSharedServices()
40+
.registerIfAbsent("testResultsOverview", TestResultsOverview.class, (spec) -> {
41+
});
4342
project.getTasks().withType(Test.class,
44-
(test) -> test.addTestListener(new FailureRecordingTestListener(testResults, test)));
45-
}
46-
47-
private TestResultsExtension getOrCreateTestResults(Project project) {
48-
TestResultsExtension testResults = project.getRootProject().getExtensions()
49-
.findByType(TestResultsExtension.class);
50-
if (testResults == null) {
51-
testResults = project.getRootProject().getExtensions().create("testResults", TestResultsExtension.class);
52-
project.getRootProject().getGradle().buildFinished(testResults::buildFinished);
53-
}
54-
return testResults;
43+
(test) -> test.addTestListener(new FailureRecordingTestListener(testResultsOverview, test)));
5544
}
5645

5746
private final class FailureRecordingTestListener implements TestListener {
5847

59-
private final List<TestFailure> failures = new ArrayList<>();
48+
private final List<TestDescriptor> failures = new ArrayList<>();
6049

61-
private final TestResultsExtension testResults;
50+
private final Provider<TestResultsOverview> testResultsOverview;
6251

6352
private final Test test;
6453

65-
private FailureRecordingTestListener(TestResultsExtension testResults, Test test) {
66-
this.testResults = testResults;
54+
private FailureRecordingTestListener(Provider<TestResultsOverview> testResultOverview, Test test) {
55+
this.testResultsOverview = testResultOverview;
6756
this.test = test;
6857
}
6958

7059
@Override
7160
public void afterSuite(TestDescriptor descriptor, TestResult result) {
7261
if (!this.failures.isEmpty()) {
73-
Collections.sort(this.failures);
74-
this.testResults.addFailures(this.test, this.failures);
62+
this.testResultsOverview.get().addFailures(this.test, this.failures);
7563
}
7664
}
7765

7866
@Override
7967
public void afterTest(TestDescriptor descriptor, TestResult result) {
8068
if (result.getFailedTestCount() > 0) {
81-
this.failures.add(new TestFailure(descriptor));
69+
this.failures.add(descriptor);
8270
}
8371
}
8472

@@ -94,55 +82,4 @@ public void beforeTest(TestDescriptor descriptor) {
9482

9583
}
9684

97-
private static final class TestFailure implements Comparable<TestFailure> {
98-
99-
private final TestDescriptor descriptor;
100-
101-
private TestFailure(TestDescriptor descriptor) {
102-
this.descriptor = descriptor;
103-
}
104-
105-
@Override
106-
public int compareTo(TestFailure other) {
107-
int comparison = this.descriptor.getClassName().compareTo(other.descriptor.getClassName());
108-
if (comparison == 0) {
109-
comparison = this.descriptor.getName().compareTo(other.descriptor.getName());
110-
}
111-
return comparison;
112-
}
113-
114-
}
115-
116-
public static class TestResultsExtension {
117-
118-
private final Map<Test, List<TestFailure>> testFailures = new TreeMap<>(
119-
(one, two) -> one.getPath().compareTo(two.getPath()));
120-
121-
private final Object monitor = new Object();
122-
123-
void addFailures(Test test, List<TestFailure> testFailures) {
124-
synchronized (this.monitor) {
125-
this.testFailures.put(test, testFailures);
126-
}
127-
}
128-
129-
public void buildFinished(BuildResult result) {
130-
synchronized (this.monitor) {
131-
if (this.testFailures.isEmpty()) {
132-
return;
133-
}
134-
System.err.println();
135-
System.err.println("Found test failures in " + this.testFailures.size() + " test task"
136-
+ ((this.testFailures.size() == 1) ? ":" : "s:"));
137-
this.testFailures.forEach((task, failures) -> {
138-
System.err.println();
139-
System.err.println(task.getPath());
140-
failures.forEach((failure) -> System.err.println(
141-
" " + failure.descriptor.getClassName() + " > " + failure.descriptor.getName()));
142-
});
143-
}
144-
}
145-
146-
}
147-
14885
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.build.testing;
18+
19+
import java.util.List;
20+
import java.util.Map;
21+
import java.util.TreeMap;
22+
import java.util.stream.Collectors;
23+
24+
import org.gradle.api.services.BuildService;
25+
import org.gradle.api.services.BuildServiceParameters;
26+
import org.gradle.api.tasks.testing.Test;
27+
import org.gradle.api.tasks.testing.TestDescriptor;
28+
import org.gradle.tooling.events.FinishEvent;
29+
import org.gradle.tooling.events.OperationCompletionListener;
30+
31+
/**
32+
* {@link BuildService} that provides an overview of all of the test failures in the
33+
* build.
34+
*
35+
* @author Andy Wilkinson
36+
*/
37+
public abstract class TestResultsOverview
38+
implements BuildService<BuildServiceParameters.None>, OperationCompletionListener, AutoCloseable {
39+
40+
private final Map<Test, List<TestFailure>> testFailures = new TreeMap<>(
41+
(one, two) -> one.getPath().compareTo(two.getPath()));
42+
43+
private final Object monitor = new Object();
44+
45+
void addFailures(Test test, List<TestDescriptor> failureDescriptors) {
46+
List<TestFailure> testFailures = failureDescriptors.stream().map(TestFailure::new).sorted()
47+
.collect(Collectors.toList());
48+
synchronized (this.monitor) {
49+
this.testFailures.put(test, testFailures);
50+
}
51+
}
52+
53+
@Override
54+
public void onFinish(FinishEvent event) {
55+
// OperationCompletionListener is implemented to defer close until the build ends
56+
}
57+
58+
@Override
59+
public void close() {
60+
synchronized (this.monitor) {
61+
if (this.testFailures.isEmpty()) {
62+
return;
63+
}
64+
System.err.println();
65+
System.err.println("Found test failures in " + this.testFailures.size() + " test task"
66+
+ ((this.testFailures.size() == 1) ? ":" : "s:"));
67+
this.testFailures.forEach((task, failures) -> {
68+
System.err.println();
69+
System.err.println(task.getPath());
70+
failures.forEach((failure) -> System.err
71+
.println(" " + failure.descriptor.getClassName() + " > " + failure.descriptor.getName()));
72+
});
73+
}
74+
}
75+
76+
private static final class TestFailure implements Comparable<TestFailure> {
77+
78+
private final TestDescriptor descriptor;
79+
80+
private TestFailure(TestDescriptor descriptor) {
81+
this.descriptor = descriptor;
82+
}
83+
84+
@Override
85+
public int compareTo(TestFailure other) {
86+
int comparison = this.descriptor.getClassName().compareTo(other.descriptor.getClassName());
87+
if (comparison == 0) {
88+
comparison = this.descriptor.getName().compareTo(other.descriptor.getName());
89+
}
90+
return comparison;
91+
}
92+
93+
}
94+
95+
}

buildSrc/src/test/java/org/springframework/boot/build/testing/TestFailuresPluginIntegrationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -84,7 +84,7 @@ void multiProjectContinue() throws IOException {
8484
void multiProjectParallel() throws IOException {
8585
createMultiProjectBuild();
8686
BuildResult result = GradleRunner.create().withDebug(true).withProjectDir(this.projectDir)
87-
.withArguments("build", "--parallel").withPluginClasspath().buildAndFail();
87+
.withArguments("build", "--parallel", "--stacktrace").withPluginClasspath().buildAndFail();
8888
assertThat(readLines(result.getOutput())).containsSequence("Found test failures in 2 test tasks:", "",
8989
":project-one:test", " example.ExampleTests > bad()", " example.ExampleTests > fail()",
9090
" example.MoreTests > bad()", " example.MoreTests > fail()", "", ":project-two:test",

0 commit comments

Comments
 (0)