Move NamingConventionsCheck into buildSrc

This will let things that don't depend on :test:framework like the
client use it.

Also skip initializing the classes we check because we don't care
about their initialization behavior because we're not executing them.
This makes the naming conventions check pretty close to instant
from a "human eye" perspective.
This commit is contained in:
Nik Everett 2016-06-03 12:48:45 -04:00
parent f132a8f1c8
commit d0e4485d42
6 changed files with 101 additions and 42 deletions

View File

@ -160,6 +160,7 @@ subprojects {
them as external dependencies so the build plugin that we use can be used
to build elasticsearch plugins outside of the elasticsearch source tree. */
ext.projectSubstitutions = [
"org.elasticsearch.gradle:build-tools:${version}": ':build-tools',
"org.elasticsearch:rest-api-spec:${version}": ':rest-api-spec',
"org.elasticsearch:elasticsearch:${version}": ':core',
"org.elasticsearch.test:framework:${version}": ':test:framework',

View File

@ -103,6 +103,7 @@ if (project == rootProject) {
url "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
test.exclude 'org/elasticsearch/test/NamingConventionsCheckBadClasses*'
}
/*****************************************************************************
@ -122,8 +123,8 @@ if (project != rootProject) {
// build-tools is not ready for primetime with these...
dependencyLicenses.enabled = false
forbiddenApisMain.enabled = false
forbiddenApisTest.enabled = false
jarHell.enabled = false
loggerUsageCheck.enabled = false
thirdPartyAudit.enabled = false
// test for elasticsearch.build tries to run with ES...
@ -137,4 +138,9 @@ if (project != rootProject) {
// the file that actually defines nocommit
exclude '**/ForbiddenPatternsTask.groovy'
}
namingConventions {
testClass = 'org.elasticsearch.test.NamingConventionsCheckBadClasses$UnitTestCase'
integTestClass = 'org.elasticsearch.test.NamingConventionsCheckBadClasses$IntegTestCase'
}
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.gradle.precommit
import org.elasticsearch.gradle.LoggedExec
import org.elasticsearch.gradle.VersionProperties
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
@ -57,8 +58,27 @@ public class NamingConventionsTask extends LoggedExec {
@Input
boolean skipIntegTestInDisguise = false
/**
* Superclass for all tests.
*/
@Input
String testClass = 'org.apache.lucene.util.LuceneTestCase'
/**
* Superclass for all integration tests.
*/
@Input
String integTestClass = 'org.elasticsearch.test.ESIntegTestCase'
public NamingConventionsTask() {
dependsOn(classpath)
// Extra classpath contains the actual test
project.configurations.create('namingConventions')
Dependency buildToolsDep = project.dependencies.add('namingConventions',
"org.elasticsearch.gradle:build-tools:${VersionProperties.elasticsearch}")
buildToolsDep.transitive = false // We don't need gradle in the classpath. It conflicts.
FileCollection extraClasspath = project.configurations.namingConventions
dependsOn(extraClasspath)
description = "Runs NamingConventionsCheck on ${classpath}"
executable = new File(project.javaHome, 'bin/java')
onlyIf { project.sourceSets.test.output.classesDir.exists() }
@ -69,7 +89,8 @@ public class NamingConventionsTask extends LoggedExec {
project.afterEvaluate {
doFirst {
args('-Djna.nosys=true')
args('-cp', classpath.asPath, 'org.elasticsearch.test.NamingConventionsCheck')
args('-cp', (classpath + extraClasspath).asPath, 'org.elasticsearch.test.NamingConventionsCheck')
args(testClass, integTestClass)
if (skipIntegTestInDisguise) {
args('--skip-integ-tests-in-disguise')
}
@ -79,7 +100,7 @@ public class NamingConventionsTask extends LoggedExec {
* process of ignoring them lets us validate that they were found so this ignore parameter acts
* as the test for the NamingConventionsCheck.
*/
if (':test:framework'.equals(project.path)) {
if (':build-tools'.equals(project.path)) {
args('--self-test')
}
args('--', project.sourceSets.test.output.classesDir.absolutePath)

View File

@ -34,7 +34,6 @@ class PrecommitTasks {
configureForbiddenApis(project),
configureCheckstyle(project),
configureNamingConventions(project),
configureLoggerUsage(project),
project.tasks.create('forbiddenPatterns', ForbiddenPatternsTask.class),
project.tasks.create('licenseHeaders', LicenseHeadersTask.class),
project.tasks.create('jarHell', JarHellTask.class),
@ -49,6 +48,20 @@ class PrecommitTasks {
UpdateShasTask updateShas = project.tasks.create('updateShas', UpdateShasTask.class)
updateShas.parentTask = dependencyLicenses
}
if (project.path != ':build-tools') {
/*
* Sadly, build-tools can't have logger-usage-check because that
* would create a circular project dependency between build-tools
* (which provides NamingConventionsCheck) and :test:logger-usage
* which provides the logger usage check. Since the build tools
* don't use the logger usage check because they don't have any
* of Elaticsearch's loggers and :test:logger-usage actually does
* use the NamingConventionsCheck we break the circular dependency
* here.
*/
precommitTasks.add(configureLoggerUsage(project))
}
Map<String, Object> precommitOptions = [
name: 'precommit',

View File

@ -25,14 +25,11 @@ import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashSet;
import java.util.Set;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.PathUtils;
/**
* Checks that all tests in a directory are named according to our naming conventions. This is important because tests that do not follow
* our conventions aren't run by gradle. This was once a glorious unit test but now that Elasticsearch is a multi-module project it must be
@ -46,11 +43,13 @@ import org.elasticsearch.common.io.PathUtils;
* {@code --self-test} that is only run in the test:framework project.
*/
public class NamingConventionsCheck {
public static void main(String[] args) throws IOException, ClassNotFoundException {
NamingConventionsCheck check = new NamingConventionsCheck();
public static void main(String[] args) throws IOException {
int i = 0;
NamingConventionsCheck check = new NamingConventionsCheck(
loadClassWithoutInitializing(args[i++]),
loadClassWithoutInitializing(args[i++]));
boolean skipIntegTestsInDisguise = false;
boolean selfTest = false;
int i = 0;
while (true) {
switch (args[i]) {
case "--skip-integ-tests-in-disguise":
@ -69,7 +68,7 @@ public class NamingConventionsCheck {
}
break;
}
check.check(PathUtils.get(args[i]));
check.check(Paths.get(args[i]));
if (selfTest) {
assertViolation("WrongName", check.missingSuffix);
@ -82,14 +81,12 @@ public class NamingConventionsCheck {
}
// Now we should have no violations
assertNoViolations("Not all subclasses of " + ESTestCase.class.getSimpleName()
assertNoViolations("Not all subclasses of " + check.testClass.getSimpleName()
+ " match the naming convention. Concrete classes must end with [Tests]", check.missingSuffix);
assertNoViolations("Classes ending with [Tests] are abstract or interfaces", check.notRunnable);
assertNoViolations("Found inner classes that are tests, which are excluded from the test runner", check.innerClasses);
String classesToSubclass = String.join(",", ESTestCase.class.getSimpleName(), ESTestCase.class.getSimpleName(),
ESTokenStreamTestCase.class.getSimpleName(), LuceneTestCase.class.getSimpleName());
assertNoViolations("Pure Unit-Test found must subclass one of [" + classesToSubclass + "]", check.pureUnitTest);
assertNoViolations("Classes ending with [Tests] must subclass [" + classesToSubclass + "]", check.notImplementing);
assertNoViolations("Pure Unit-Test found must subclass [" + check.testClass.getSimpleName() + "]", check.pureUnitTest);
assertNoViolations("Classes ending with [Tests] must subclass [" + check.testClass.getSimpleName() + "]", check.notImplementing);
if (!skipIntegTestsInDisguise) {
assertNoViolations("Subclasses of ESIntegTestCase should end with IT as they are integration tests",
check.integTestsInDisguise);
@ -103,6 +100,14 @@ public class NamingConventionsCheck {
private final Set<Class<?>> notRunnable = new HashSet<>();
private final Set<Class<?>> innerClasses = new HashSet<>();
private final Class<?> testClass;
private final Class<?> integTestClass;
public NamingConventionsCheck(Class<?> testClass, Class<?> integTestClass) {
this.testClass = testClass;
this.integTestClass = integTestClass;
}
public void check(Path rootPath) throws IOException {
Files.walkFileTree(rootPath, new FileVisitor<Path>() {
/**
@ -136,9 +141,9 @@ public class NamingConventionsCheck {
String filename = file.getFileName().toString();
if (filename.endsWith(".class")) {
String className = filename.substring(0, filename.length() - ".class".length());
Class<?> clazz = loadClass(className);
Class<?> clazz = loadClassWithoutInitializing(packageName + className);
if (clazz.getName().endsWith("Tests")) {
if (ESIntegTestCase.class.isAssignableFrom(clazz)) {
if (integTestClass.isAssignableFrom(clazz)) {
integTestsInDisguise.add(clazz);
}
if (Modifier.isAbstract(clazz.getModifiers()) || Modifier.isInterface(clazz.getModifiers())) {
@ -164,15 +169,7 @@ public class NamingConventionsCheck {
}
private boolean isTestCase(Class<?> clazz) {
return LuceneTestCase.class.isAssignableFrom(clazz);
}
private Class<?> loadClass(String className) {
try {
return Thread.currentThread().getContextClassLoader().loadClass(packageName + className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return testClass.isAssignableFrom(clazz);
}
@Override
@ -186,7 +183,6 @@ public class NamingConventionsCheck {
* Fail the process if there are any violations in the set. Named to look like a junit assertion even though it isn't because it is
* similar enough.
*/
@SuppressForbidden(reason = "System.err/System.exit")
private static void assertNoViolations(String message, Set<Class<?>> set) {
if (false == set.isEmpty()) {
System.err.println(message + ":");
@ -201,10 +197,9 @@ public class NamingConventionsCheck {
* Fail the process if we didn't detect a particular violation. Named to look like a junit assertion even though it isn't because it is
* similar enough.
*/
@SuppressForbidden(reason = "System.err/System.exit")
private static void assertViolation(String className, Set<Class<?>> set) throws ClassNotFoundException {
className = "org.elasticsearch.test.test.NamingConventionsCheckBadClasses$" + className;
if (false == set.remove(Class.forName(className))) {
private static void assertViolation(String className, Set<Class<?>> set) {
className = "org.elasticsearch.test.NamingConventionsCheckBadClasses$" + className;
if (false == set.remove(loadClassWithoutInitializing(className))) {
System.err.println("Error in NamingConventionsCheck! Expected [" + className + "] to be a violation but wasn't.");
System.exit(1);
}
@ -213,9 +208,20 @@ public class NamingConventionsCheck {
/**
* Fail the process with the provided message.
*/
@SuppressForbidden(reason = "System.err/System.exit")
private static void fail(String reason) {
System.err.println(reason);
System.exit(1);
}
static Class<?> loadClassWithoutInitializing(String name) {
try {
return Class.forName(name,
// Don't initialize the class to save time. Not needed for this test and this doesn't share a VM with any other tests.
false,
// Use our classloader rather than the bootstrap class loader.
NamingConventionsCheck.class.getClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -17,9 +17,7 @@
* under the License.
*/
package org.elasticsearch.test.test;
import org.elasticsearch.test.ESTestCase;
package org.elasticsearch.test;
import junit.framework.TestCase;
@ -30,21 +28,35 @@ public class NamingConventionsCheckBadClasses {
public static final class NotImplementingTests {
}
public static final class WrongName extends ESTestCase {
public static final class WrongName extends UnitTestCase {
/*
* Dummy test so the tests pass. We do this *and* skip the tests so anyone who jumps back to a branch without these tests can still
* compile without a failure. That is because clean doesn't actually clean these....
*/
public void testDummy() {}
}
public static abstract class DummyAbstractTests extends ESTestCase {
public static abstract class DummyAbstractTests extends UnitTestCase {
}
public interface DummyInterfaceTests {
}
public static final class InnerTests extends ESTestCase {
public static final class InnerTests extends UnitTestCase {
public void testDummy() {}
}
public static final class WrongNameTheSecond extends ESTestCase {
public static final class WrongNameTheSecond extends UnitTestCase {
public void testDummy() {}
}
public static final class PlainUnit extends TestCase {
public void testDummy() {}
}
public abstract static class UnitTestCase extends TestCase {
}
public abstract static class IntegTestCase extends UnitTestCase {
}
}