Skip to content

Commit 8b32091

Browse files
committed
Merge branch 'master' into java-time
2 parents bf3a942 + 5e6460a commit 8b32091

File tree

152 files changed

+3535
-1108
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

152 files changed

+3535
-1108
lines changed

buildSrc/src/main/groovy/org/elasticsearch/gradle/BuildPlugin.groovy

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,14 @@ class BuildPlugin implements Plugin<Project> {
155155
println " Gradle Version : ${project.gradle.gradleVersion}"
156156
println " OS Info : ${System.getProperty('os.name')} ${System.getProperty('os.version')} (${System.getProperty('os.arch')})"
157157
if (gradleJavaVersionDetails != compilerJavaVersionDetails || gradleJavaVersionDetails != runtimeJavaVersionDetails) {
158-
println " Compiler JDK Version : ${getPaddedMajorVersion(compilerJavaVersionEnum)} (${compilerJavaVersionDetails})"
158+
println " Compiler JDK Version : ${compilerJavaVersionEnum} (${compilerJavaVersionDetails})"
159159
println " Compiler java.home : ${compilerJavaHome}"
160-
println " Runtime JDK Version : ${getPaddedMajorVersion(runtimeJavaVersionEnum)} (${runtimeJavaVersionDetails})"
160+
println " Runtime JDK Version : ${runtimeJavaVersionEnum} (${runtimeJavaVersionDetails})"
161161
println " Runtime java.home : ${runtimeJavaHome}"
162-
println " Gradle JDK Version : ${getPaddedMajorVersion(JavaVersion.toVersion(gradleJavaVersion))} (${gradleJavaVersionDetails})"
162+
println " Gradle JDK Version : ${JavaVersion.toVersion(gradleJavaVersion)} (${gradleJavaVersionDetails})"
163163
println " Gradle java.home : ${gradleJavaHome}"
164164
} else {
165-
println " JDK Version : ${getPaddedMajorVersion(JavaVersion.toVersion(gradleJavaVersion))} (${gradleJavaVersionDetails})"
165+
println " JDK Version : ${JavaVersion.toVersion(gradleJavaVersion)} (${gradleJavaVersionDetails})"
166166
println " JAVA_HOME : ${gradleJavaHome}"
167167
}
168168
println " Random Testing Seed : ${project.testSeed}"
@@ -232,10 +232,6 @@ class BuildPlugin implements Plugin<Project> {
232232
project.ext.java9Home = project.rootProject.ext.java9Home
233233
}
234234

235-
private static String getPaddedMajorVersion(JavaVersion compilerJavaVersionEnum) {
236-
compilerJavaVersionEnum.getMajorVersion().toString().padLeft(2)
237-
}
238-
239235
private static String findCompilerJavaHome() {
240236
final String compilerJavaHome = System.getenv('JAVA_HOME')
241237
final String compilerJavaProperty = System.getProperty('compiler.java')

buildSrc/src/main/groovy/org/elasticsearch/gradle/precommit/PrecommitTasks.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class PrecommitTasks {
4848
project.tasks.create('licenseHeaders', LicenseHeadersTask.class),
4949
project.tasks.create('filepermissions', FilePermissionsTask.class),
5050
configureJarHell(project),
51-
configureThirdPartyAudit(project)
51+
configureThirdPartyAudit(project),
52+
configureTestingConventions(project)
5253
]
5354

5455
// tasks with just tests don't need dependency licenses, so this flag makes adding
@@ -89,6 +90,10 @@ class PrecommitTasks {
8990
])
9091
}
9192

93+
static Task configureTestingConventions(Project project) {
94+
project.getTasks().create("testingConventions", TestingConventionsTasks.class)
95+
}
96+
9297
private static Task configureJarHell(Project project) {
9398
Task task = project.tasks.create('jarHell', JarHellTask.class)
9499
task.classpath = project.sourceSets.test.runtimeClasspath

buildSrc/src/main/java/org/elasticsearch/gradle/precommit/FilePermissionsTask.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,14 @@
2828
import java.util.stream.Collectors;
2929

3030
import org.apache.tools.ant.taskdefs.condition.Os;
31+
import org.elasticsearch.gradle.tool.Boilerplate;
3132
import org.gradle.api.DefaultTask;
3233
import org.gradle.api.GradleException;
3334
import org.gradle.api.file.FileCollection;
3435
import org.gradle.api.file.FileTree;
35-
import org.gradle.api.plugins.JavaPluginConvention;
3636
import org.gradle.api.tasks.InputFiles;
3737
import org.gradle.api.tasks.OutputFile;
3838
import org.gradle.api.tasks.SkipWhenEmpty;
39-
import org.gradle.api.tasks.SourceSetContainer;
4039
import org.gradle.api.tasks.StopExecutionException;
4140
import org.gradle.api.tasks.TaskAction;
4241
import org.gradle.api.tasks.util.PatternFilterable;
@@ -81,8 +80,7 @@ private static boolean isExecutableFile(File file) {
8180
@InputFiles
8281
@SkipWhenEmpty
8382
public FileCollection getFiles() {
84-
SourceSetContainer sourceSets = getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
85-
return sourceSets.stream()
83+
return Boilerplate.getJavaSourceSets(getProject()).stream()
8684
.map(sourceSet -> sourceSet.getAllSource().matching(filesFilter))
8785
.reduce(FileTree::plus)
8886
.orElse(getProject().files().getAsFileTree());
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.gradle.precommit;
20+
21+
import org.elasticsearch.gradle.tool.Boilerplate;
22+
import org.gradle.api.DefaultTask;
23+
import org.gradle.api.file.FileCollection;
24+
import org.gradle.api.tasks.Input;
25+
import org.gradle.api.tasks.OutputFile;
26+
import org.gradle.api.tasks.SkipWhenEmpty;
27+
import org.gradle.api.tasks.TaskAction;
28+
29+
import java.io.File;
30+
import java.io.IOException;
31+
import java.lang.annotation.Annotation;
32+
import java.lang.reflect.Method;
33+
import java.lang.reflect.Modifier;
34+
import java.net.MalformedURLException;
35+
import java.net.URL;
36+
import java.net.URLClassLoader;
37+
import java.nio.file.FileVisitResult;
38+
import java.nio.file.FileVisitor;
39+
import java.nio.file.Files;
40+
import java.nio.file.Path;
41+
import java.nio.file.StandardOpenOption;
42+
import java.nio.file.attribute.BasicFileAttributes;
43+
import java.util.ArrayList;
44+
import java.util.List;
45+
import java.util.function.Predicate;
46+
import java.util.stream.Collectors;
47+
import java.util.stream.Stream;
48+
49+
public class TestingConventionsTasks extends DefaultTask {
50+
51+
private static final String TEST_CLASS_SUFIX = "Tests";
52+
private static final String INTEG_TEST_CLASS_SUFIX = "IT";
53+
private static final String TEST_METHOD_PREFIX = "test";
54+
55+
/**
56+
* Are there tests to execute ? Accounts for @Ignore and @AwaitsFix
57+
*/
58+
private Boolean activeTestsExists;
59+
60+
private List<String> testClassNames;
61+
62+
public TestingConventionsTasks() {
63+
setDescription("Tests various testing conventions");
64+
// Run only after everything is compiled
65+
Boilerplate.getJavaSourceSets(getProject()).all(sourceSet -> dependsOn(sourceSet.getClassesTaskName()));
66+
}
67+
68+
@TaskAction
69+
public void doCheck() throws IOException {
70+
activeTestsExists = false;
71+
final List<String> problems;
72+
73+
try (URLClassLoader isolatedClassLoader = new URLClassLoader(
74+
getTestsClassPath().getFiles().stream().map(this::fileToUrl).toArray(URL[]::new)
75+
)) {
76+
List<? extends Class<?>> classes = getTestClassNames().stream()
77+
.map(name -> loadClassWithoutInitializing(name, isolatedClassLoader))
78+
.collect(Collectors.toList());
79+
80+
Predicate<Class<?>> isStaticClass = clazz -> Modifier.isStatic(clazz.getModifiers());
81+
Predicate<Class<?>> isPublicClass = clazz -> Modifier.isPublic(clazz.getModifiers());
82+
Predicate<Class<?>> implementsNamingConvention = clazz -> clazz.getName().endsWith(TEST_CLASS_SUFIX) ||
83+
clazz.getName().endsWith(INTEG_TEST_CLASS_SUFIX);
84+
85+
problems = Stream.concat(
86+
checkNoneExists(
87+
"Test classes implemented by inner classes will not run",
88+
classes.stream()
89+
.filter(isStaticClass)
90+
.filter(implementsNamingConvention.or(this::seemsLikeATest))
91+
).stream(),
92+
checkNoneExists(
93+
"Seem like test classes but don't match naming convention",
94+
classes.stream()
95+
.filter(isStaticClass.negate())
96+
.filter(isPublicClass)
97+
.filter(this::seemsLikeATest)
98+
.filter(implementsNamingConvention.negate())
99+
).stream()
100+
).collect(Collectors.toList());
101+
}
102+
103+
if (problems.isEmpty()) {
104+
getSuccessMarker().getParentFile().mkdirs();
105+
Files.write(getSuccessMarker().toPath(), new byte[]{}, StandardOpenOption.CREATE);
106+
} else {
107+
problems.forEach(getProject().getLogger()::error);
108+
throw new IllegalStateException("Testing conventions are not honored");
109+
}
110+
}
111+
112+
@Input
113+
@SkipWhenEmpty
114+
public List<String> getTestClassNames() {
115+
if (testClassNames == null) {
116+
testClassNames = Boilerplate.getJavaSourceSets(getProject()).getByName("test").getOutput().getClassesDirs()
117+
.getFiles().stream()
118+
.filter(File::exists)
119+
.flatMap(testRoot -> walkPathAndLoadClasses(testRoot).stream())
120+
.collect(Collectors.toList());
121+
}
122+
return testClassNames;
123+
}
124+
125+
@OutputFile
126+
public File getSuccessMarker() {
127+
return new File(getProject().getBuildDir(), "markers/" + getName());
128+
}
129+
130+
private List<String> checkNoneExists(String message, Stream<? extends Class<?>> stream) {
131+
List<String> problems = new ArrayList<>();
132+
List<Class<?>> entries = stream.collect(Collectors.toList());
133+
if (entries.isEmpty() == false) {
134+
problems.add(message + ":");
135+
entries.stream()
136+
.map(each -> " * " + each.getName())
137+
.forEach(problems::add);
138+
}
139+
return problems;
140+
}
141+
142+
private boolean seemsLikeATest(Class<?> clazz) {
143+
try {
144+
ClassLoader classLoader = clazz.getClassLoader();
145+
Class<?> junitTest;
146+
try {
147+
junitTest = classLoader.loadClass("junit.framework.Test");
148+
} catch (ClassNotFoundException e) {
149+
throw new IllegalStateException("Could not load junit.framework.Test. It's expected that this class is " +
150+
"available on the tests classpath");
151+
}
152+
if (junitTest.isAssignableFrom(clazz)) {
153+
getLogger().info("{} is a test because it extends junit.framework.Test", clazz.getName());
154+
return true;
155+
}
156+
for (Method method : clazz.getMethods()) {
157+
if (matchesTestMethodNamingConvention(clazz, method)) return true;
158+
if (isAnnotated(clazz, method, junitTest)) return true;
159+
}
160+
return false;
161+
} catch (NoClassDefFoundError e) {
162+
throw new IllegalStateException("Failed to inspect class " + clazz.getName(), e);
163+
}
164+
}
165+
166+
private boolean matchesTestMethodNamingConvention(Class<?> clazz, Method method) {
167+
if (method.getName().startsWith(TEST_METHOD_PREFIX) &&
168+
Modifier.isStatic(method.getModifiers()) == false &&
169+
method.getReturnType().equals(Void.class)
170+
) {
171+
getLogger().info("{} is a test because it has method: {}", clazz.getName(), method.getName());
172+
return true;
173+
}
174+
return false;
175+
}
176+
177+
private boolean isAnnotated(Class<?> clazz, Method method, Class<?> annotation) {
178+
for (Annotation presentAnnotation : method.getAnnotations()) {
179+
if (annotation.isAssignableFrom(presentAnnotation.getClass())) {
180+
getLogger().info("{} is a test because {} is annotated with junit.framework.Test",
181+
clazz.getName(), method.getName()
182+
);
183+
return true;
184+
}
185+
}
186+
return false;
187+
}
188+
189+
private FileCollection getTestsClassPath() {
190+
// This is doesn't need to be annotated with @Classpath because we only really care about the test source set
191+
return getProject().files(
192+
getProject().getConfigurations().getByName("testCompile").resolve(),
193+
Boilerplate.getJavaSourceSets(getProject())
194+
.stream()
195+
.flatMap(sourceSet -> sourceSet.getOutput().getClassesDirs().getFiles().stream())
196+
.collect(Collectors.toList())
197+
);
198+
}
199+
200+
private List<String> walkPathAndLoadClasses(File testRoot) {
201+
List<String> classes = new ArrayList<>();
202+
try {
203+
Files.walkFileTree(testRoot.toPath(), new FileVisitor<Path>() {
204+
private String packageName;
205+
206+
@Override
207+
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
208+
// First we visit the root directory
209+
if (packageName == null) {
210+
// And it package is empty string regardless of the directory name
211+
packageName = "";
212+
} else {
213+
packageName += dir.getFileName() + ".";
214+
}
215+
return FileVisitResult.CONTINUE;
216+
}
217+
218+
@Override
219+
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
220+
// Go up one package by jumping back to the second to last '.'
221+
packageName = packageName.substring(0, 1 + packageName.lastIndexOf('.', packageName.length() - 2));
222+
return FileVisitResult.CONTINUE;
223+
}
224+
225+
@Override
226+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
227+
String filename = file.getFileName().toString();
228+
if (filename.endsWith(".class")) {
229+
String className = filename.substring(0, filename.length() - ".class".length());
230+
classes.add(packageName + className);
231+
}
232+
return FileVisitResult.CONTINUE;
233+
}
234+
235+
@Override
236+
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
237+
throw new IOException("Failed to visit " + file, exc);
238+
}
239+
});
240+
} catch (IOException e) {
241+
throw new IllegalStateException(e);
242+
}
243+
return classes;
244+
}
245+
246+
private Class<?> loadClassWithoutInitializing(String name, ClassLoader isolatedClassLoader) {
247+
try {
248+
return Class.forName(name,
249+
// Don't initialize the class to save time. Not needed for this test and this doesn't share a VM with any other tests.
250+
false,
251+
isolatedClassLoader
252+
);
253+
} catch (ClassNotFoundException e) {
254+
// Will not get here as the exception will be loaded by isolatedClassLoader
255+
throw new RuntimeException("Failed to load class " + name, e);
256+
}
257+
}
258+
259+
private URL fileToUrl(File file) {
260+
try {
261+
return file.toURI().toURL();
262+
} catch (MalformedURLException e) {
263+
throw new IllegalStateException(e);
264+
}
265+
}
266+
267+
}

0 commit comments

Comments
 (0)