Files
github-api/src/test/java/org/kohsuke/github/ArchTests.java

241 lines
11 KiB
Java

package org.kohsuke.github;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.*;
import com.tngtech.archunit.core.domain.properties.HasName;
import com.tngtech.archunit.core.domain.properties.HasOwner;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.Closeable;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type;
import static com.tngtech.archunit.core.domain.JavaClass.namesOf;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining;
import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner;
import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes;
import static com.tngtech.archunit.lang.conditions.ArchConditions.*;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
public class ArchTests {
private static final JavaClasses classFiles = new ClassFileImporter()
.withImportOption(new ImportOption.DoNotIncludeTests())
.withImportOption(new ImportOption.DoNotIncludeJars())
.importPackages("org.kohsuke.github");
private static final JavaClasses apacheCommons = new ClassFileImporter().importPackages("org.apache.commons.lang3");
private static final JavaClasses testClassFiles = new ClassFileImporter()
.withImportOption(new ImportOption.OnlyIncludeTests())
.withImportOption(new ImportOption.DoNotIncludeJars())
.importPackages("org.kohsuke.github");
private static final DescribedPredicate<JavaAnnotation<?>> previewAnnotationWithNoMediaType = new DescribedPredicate<JavaAnnotation<?>>(
"preview has no required media types defined") {
@Override
public boolean apply(JavaAnnotation<?> javaAnnotation) {
boolean isPreview = javaAnnotation.getRawType().isEquivalentTo(Preview.class);
Object[] values = (Object[]) javaAnnotation.getProperties().get("value");
return isPreview && values != null && values.length < 1;
}
};
@BeforeClass
public static void beforeClass() {
assertThat(classFiles.size(), greaterThan(0));
}
@Test
public void testPreviewsAreFlaggedAsDeprecated() {
String reason = "all preview APIs must be annotated as @Deprecated until they are promoted to stable";
ArchRule classRule = classes().that()
.areAnnotatedWith(Preview.class)
.should()
.beAnnotatedWith(Deprecated.class)
.andShould(not(beAnnotatedWith(previewAnnotationWithNoMediaType)))
.because(reason);
ArchRule methodRule = methods().that()
.areAnnotatedWith(Preview.class)
.should()
.beAnnotatedWith(Deprecated.class)
.andShould(not(beAnnotatedWith(previewAnnotationWithNoMediaType)))
.because(reason);
ArchRule enumFieldsRule = fields().that()
.areDeclaredInClassesThat()
.areEnums()
.and()
.areAnnotatedWith(Preview.class)
.should()
.beAnnotatedWith(Deprecated.class)
.andShould(not(beAnnotatedWith(previewAnnotationWithNoMediaType)))
.because(reason);
classRule.check(classFiles);
enumFieldsRule.check(classFiles);
methodRule.check(classFiles);
}
@Test
public void testBetaApisAreFlaggedAsDeprecated() {
String reason = "all beta APIs must be annotated as @Deprecated until they are promoted to stable";
ArchRule classRule = classes().that()
.areAnnotatedWith(BetaApi.class)
.should()
.beAnnotatedWith(Deprecated.class)
.because(reason);
ArchRule methodRule = methods().that()
.areAnnotatedWith(BetaApi.class)
.should()
.beAnnotatedWith(Deprecated.class)
.because(reason);
ArchRule enumFieldsRule = fields().that()
.areDeclaredInClassesThat()
.areEnums()
.and()
.areAnnotatedWith(BetaApi.class)
.should()
.beAnnotatedWith(Deprecated.class)
.because(reason);
classRule.check(classFiles);
enumFieldsRule.check(classFiles);
methodRule.check(classFiles);
}
@Test
public void testRequireUseOfAssertThat() {
final String reason = "This project uses `assertThat(...)` instead of other `assert*()` methods.";
final DescribedPredicate<HasName> assertMethodOtherThanAssertThat = nameContaining("assert")
.and(DescribedPredicate.not(name("assertThat")));
final ArchRule onlyAssertThatRule = classes()
.should(not(callMethodWhere(target(assertMethodOtherThanAssertThat))))
.because(reason);
onlyAssertThatRule.check(testClassFiles);
}
@Test
public void testRequireUseOfOnlySpecificApacheCommons() {
final ArchRule onlyApprovedApacheCommonsLang3Methods = classes()
.should(notCallMethodsInPackageUnless("org.apache.commons..",
// unless it is one of these methods
targetMethodIs(StringUtils.class, "capitalize", String.class),
targetMethodIs(StringUtils.class, "defaultString", String.class, String.class),
targetMethodIs(StringUtils.class, "equals", CharSequence.class, CharSequence.class),
targetMethodIs(StringUtils.class, "isBlank", CharSequence.class),
targetMethodIs(StringUtils.class, "isEmpty", CharSequence.class),
targetMethodIs(StringUtils.class, "join", Iterable.class, String.class),
targetMethodIs(StringUtils.class,
"prependIfMissing",
String.class,
CharSequence.class,
CharSequence[].class),
targetMethodIs(ToStringBuilder.class, "toString"),
targetMethodIs(ToStringBuilder.class, "append", String.class, Object.class),
targetMethodIs(ToStringBuilder.class, "append", String.class, long.class),
targetMethodIs(ToStringBuilder.class, "append", String.class, int.class),
targetMethodIs(ToStringBuilder.class, "isEmpty"),
targetMethodIs(ToStringBuilder.class, "equals"),
targetMethodIs(ToStringBuilder.class, "capitalize"),
targetMethodIs(ToStringStyle.class,
"append",
StringBuffer.class,
String.class,
Object.class,
Boolean.class),
targetMethodIs(ReflectionToStringBuilder.class, "accept", Field.class),
targetMethodIs(IOUtils.class, "closeQuietly", InputStream.class),
targetMethodIs(IOUtils.class, "closeQuietly", Closeable.class),
targetMethodIs(IOUtils.class, "toString", InputStream.class, Charset.class),
targetMethodIs(IOUtils.class, "toString", Reader.class),
targetMethodIs(IOUtils.class, "toByteArray", InputStream.class)))
.because(
"Commons methods must be manually verified to be compatible with commons-io:2.4 or earlier and commons-lang3:3.9 or earlier should be used.");
onlyApprovedApacheCommonsLang3Methods.check(classFiles);
}
public static ArchCondition<JavaClass> notCallMethodsInPackageUnless(final String packageIdentifier,
final DescribedPredicate<JavaCall<?>>... unlessPredicates) {
DescribedPredicate<JavaCall<?>> restrictedPackageCalls = target(
HasOwner.Predicates.With.<JavaClass>owner(resideInAPackage(packageIdentifier)));
if (unlessPredicates.length > 0) {
DescribedPredicate<JavaCall<?>> allowed = unlessPredicates[0];
for (int x = 1; x < unlessPredicates.length; x++) {
allowed = allowed.or(unlessPredicates[x]);
}
restrictedPackageCalls = unless(restrictedPackageCalls, allowed);
}
return not(callMethodWhere(restrictedPackageCalls));
}
public static DescribedPredicate<JavaCall<?>> targetMethodIs(Class<?> owner,
String methodName,
Class<?>... parameterTypes) {
return JavaCall.Predicates.target(owner(type(owner)))
.and(JavaCall.Predicates.target(name(methodName)))
.and(JavaCall.Predicates.target(rawParameterTypes(parameterTypes)))
.as("method is %s",
Formatters.formatMethodSimple(owner.getSimpleName(), methodName, namesOf(parameterTypes)));
}
public static <T> DescribedPredicate<T> unless(DescribedPredicate<? super T> first,
DescribedPredicate<? super T> second) {
return new UnlessPredicate(first, second);
}
private static class UnlessPredicate<T> extends DescribedPredicate<T> {
private final DescribedPredicate<T> current;
private final DescribedPredicate<? super T> other;
UnlessPredicate(DescribedPredicate<T> current, DescribedPredicate<? super T> other) {
super(current.getDescription() + " unless " + other.getDescription());
this.current = checkNotNull(current);
this.other = checkNotNull(other);
}
@Override
public boolean apply(T input) {
return current.apply(input) && !other.apply(input);
}
}
}