/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch;
import junit.framework.TestCase;
import com.google.common.base.Joiner;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.ESTokenStreamTestCase;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URISyntaxException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashSet;
import java.util.Set;
/**
* Simple class that ensures that all subclasses concrete of ESTestCase end with either Test | Tests
*/
public class NamingConventionTests extends ESTestCase {
// see https://github.com/elasticsearch/elasticsearch/issues/9945
public void testNamingConventions()
throws ClassNotFoundException, IOException, URISyntaxException {
final Set<Class> notImplementing = new HashSet<>();
final Set<Class> pureUnitTest = new HashSet<>();
final Set<Class> missingSuffix = new HashSet<>();
final Set<Class> integTestsInDisguise = new HashSet<>();
final Set<Class> notRunnable = new HashSet<>();
final Set<Class> innerClasses = new HashSet<>();
String[] packages = {"org.elasticsearch", "org.apache.lucene"};
for (final String packageName : packages) {
final String path = "/" + packageName.replace('.', '/');
final Path startPath = getDataPath(path);
Files.walkFileTree(startPath, new FileVisitor<Path>() {
private Path pkgPrefix = PathUtils.get(path).getParent();
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
pkgPrefix = pkgPrefix.resolve(dir.getFileName());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
try {
String filename = file.getFileName().toString();
if (filename.endsWith(".class")) {
Class<?> clazz = loadClass(filename);
if (clazz.getName().endsWith("Tests")) { // don't worry about the ones that match the pattern
if (ESIntegTestCase.class.isAssignableFrom(clazz)) {
integTestsInDisguise.add(clazz);
}
if (Modifier.isAbstract(clazz.getModifiers()) || Modifier.isInterface(clazz.getModifiers())) {
notRunnable.add(clazz);
} else if (isTestCase(clazz) == false) {
notImplementing.add(clazz);
} else if (Modifier.isStatic(clazz.getModifiers())) {
innerClasses.add(clazz);
}
} else if (clazz.getName().endsWith("IT")) {
if (isTestCase(clazz) == false) {
notImplementing.add(clazz);
}
// otherwise fine
} else if (Modifier.isAbstract(clazz.getModifiers()) == false && Modifier.isInterface(clazz.getModifiers()) == false) {
if (isTestCase(clazz)) {
missingSuffix.add(clazz);
} else if (junit.framework.Test.class.isAssignableFrom(clazz) || hasTestAnnotation(clazz)) {
pureUnitTest.add(clazz);
}
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return FileVisitResult.CONTINUE;
}
private boolean hasTestAnnotation(Class<?> clazz) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.getAnnotation(Test.class) != null) {
return true;
}
}
return false;
}
private boolean isTestCase(Class<?> clazz) {
return LuceneTestCase.class.isAssignableFrom(clazz);
}
private Class<?> loadClass(String filename) throws ClassNotFoundException {
StringBuilder pkg = new StringBuilder();
for (Path p : pkgPrefix) {
pkg.append(p.getFileName().toString()).append(".");
}
pkg.append(filename.substring(0, filename.length() - 6));
return Thread.currentThread().getContextClassLoader().loadClass(pkg.toString());
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
throw exc;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
pkgPrefix = pkgPrefix.getParent();
return FileVisitResult.CONTINUE;
}
});
}
assertTrue(missingSuffix.remove(WrongName.class));
assertTrue(missingSuffix.remove(WrongNameTheSecond.class));
assertTrue(notRunnable.remove(DummyAbstractTests.class));
assertTrue(notRunnable.remove(DummyInterfaceTests.class));
assertTrue(innerClasses.remove(InnerTests.class));
assertTrue(notImplementing.remove(NotImplementingTests.class));
assertTrue(pureUnitTest.remove(PlainUnit.class));
assertTrue(pureUnitTest.remove(PlainUnitTheSecond.class));
String classesToSubclass = Joiner.on(',').join(
ESTestCase.class.getSimpleName(),
ESTestCase.class.getSimpleName(),
ESTokenStreamTestCase.class.getSimpleName(),
LuceneTestCase.class.getSimpleName());
assertTrue("Not all subclasses of " + ESTestCase.class.getSimpleName() +
" match the naming convention. Concrete classes must end with [Tests]:\n" + Joiner.on('\n').join(missingSuffix),
missingSuffix.isEmpty());
assertTrue("Classes ending with [Tests] are abstract or interfaces:\n" + Joiner.on('\n').join(notRunnable),
notRunnable.isEmpty());
assertTrue("Found inner classes that are tests, which are excluded from the test runner:\n" + Joiner.on('\n').join(innerClasses),
innerClasses.isEmpty());
assertTrue("Pure Unit-Test found must subclass one of [" + classesToSubclass +"]:\n" + Joiner.on('\n').join(pureUnitTest),
pureUnitTest.isEmpty());
assertTrue("Classes ending with [Tests] must subclass [" + classesToSubclass + "]:\n" + Joiner.on('\n').join(notImplementing),
notImplementing.isEmpty());
assertTrue("Subclasses of ESIntegTestCase should end with IT as they are integration tests:\n" + Joiner.on('\n').join(integTestsInDisguise),
integTestsInDisguise.isEmpty());
}
/*
* Some test the test classes
*/
public static final class NotImplementingTests {}
public static final class WrongName extends ESTestCase {}
public static abstract class DummyAbstractTests extends ESTestCase {}
public interface DummyInterfaceTests {}
public static final class InnerTests extends ESTestCase {}
public static final class WrongNameTheSecond extends ESTestCase {}
public static final class PlainUnit extends TestCase {}
public static final class PlainUnitTheSecond {
@Test
public void foo() {
}
}
}