Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add dynamic Truth8 support resolve OptionalSubject discovery issue #87

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@
<version>1.2.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
import org.reflections.Reflections;

import java.math.BigDecimal;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.*;

import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
Expand All @@ -34,7 +34,7 @@ public class BuiltInSubjectTypeStore {
* In priority order - most specific first. Types that are native to {@link Truth} - i.e. you can call {@link
* Truth#assertThat}(...) with it. Note that this does not include {@link Truth8} types.
*/
@Getter(AccessLevel.PROTECTED)
@Getter(AccessLevel.PRIVATE)
private static final HashSet<Class<?>> nativeTypes = new LinkedHashSet<>();

@Getter(AccessLevel.PRIVATE)
Expand All @@ -43,31 +43,49 @@ public class BuiltInSubjectTypeStore {
@Getter(AccessLevel.PRIVATE)
private static final Map<String, Class<? extends Subject>> classPathSubjectTypes = new HashMap<>();

/**
* Higher priority first.
*/
static Class<?>[] classes = {
Map.class,
Iterable.class,
List.class,
Set.class,
Throwable.class,
BigDecimal.class,
String.class,
Double.class,
Long.class,
Integer.class,
Short.class,
Number.class,
Boolean.class,
Comparable.class,
Class.class, // Enum#getDeclaringClass
Object.class, // catch all - uses plain Subject.class
};

/**
* {@link Path} is excluded, because Turth8's {@link com.google.common.truth.PathSubject} is empty, so our generated
* PathSubject is superior.
*/
static Class<?>[] classesFromTruth8 = {
// Truth8
// Path.class,
OptionalDouble.class,
OptionalInt.class,
OptionalLong.class,
Stream.class,
IntStream.class,
DoubleStream.class,
LongStream.class,
};

static {
// higher priority first
Class<?>[] classes = {
Map.class,
Iterable.class,
List.class,
Set.class,
Throwable.class,
BigDecimal.class,
String.class,
Double.class,
Long.class,
Integer.class,
Short.class,
Number.class,
Boolean.class,
Comparable.class,
Class.class, // Enum#getDeclaringClass
Object.class, // catch all - uses plain Subject.class
};
nativeTypes.addAll(Arrays.stream(classes).collect(Collectors.toList()));

//
nativeTypesTruth8.add(Optional.class);
nativeTypesTruth8.add(Stream.class);
nativeTypesTruth8.addAll(Arrays.stream(classesFromTruth8).collect(Collectors.toList()));
}

static {
Expand All @@ -87,6 +105,13 @@ public BuiltInSubjectTypeStore() {
autoRegisterStandardSubjectExtension();
}

public static boolean isANativeType(Class<?> aClass) {
return Stream.concat(
getNativeTypes().stream(),
getNativeTypesTruth8().stream())
.anyMatch(aClass::equals);
}

protected void autoRegisterStandardSubjectExtension() {
Set<Class<?>> nativeExtensions = classUtils.findNativeExtensions("io.stubbs");
for (Class<?> nativeExtension : nativeExtensions) {
Expand Down Expand Up @@ -116,6 +141,10 @@ private static void initSubjectTypes() {
subjectTypes.forEach(x -> classPathSubjectTypes.put(x.getSimpleName(), x));
}

public static boolean hasStaticAccessThroughTruthEntryPoint(final Class<?> returnType) {
return getNativeTypes().contains(returnType);
}

/**
* Should only do this, if we can't find a more specific subject for the returnType.
*/
Expand All @@ -128,14 +157,17 @@ public boolean isTypeCoveredUnderStandardSubjects(final Class<?> returnType) {
? returnType.getComponentType()
: returnType;

List<Class<?>> assignable = nativeTypes.stream().filter(x ->
x.isAssignableFrom(normalised)
).collect(Collectors.toList());
boolean isCoveredByNonPrimitiveStandardSubjects = !assignable.isEmpty();
boolean isCoveredByNonPrimitiveStandardSubjects = Stream.concat(
getNativeTypes().stream(),
getNativeTypesTruth8().stream()
)
.anyMatch(x ->
x.isAssignableFrom(normalised)
);

boolean array = returnType.isArray();
boolean isAnArray = returnType.isArray();

return isCoveredByNonPrimitiveStandardSubjects || array;
return isCoveredByNonPrimitiveStandardSubjects || isAnArray;
}

public Optional<Class<? extends Subject>> getSubjectForNotNativeType(String simpleName, Class<?> clazzUnderTest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class JDKOverrideAnalyser {
public boolean doesOverrideClassContainMethod(Class<?> clazz, Method method) {
Optional<ClassFile> classModel = getCachedClass(clazz);

JDKPlatformProvider

return classModel
.filter(model ->
doesContainsMethod(model, method))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ private void recursive(Class<?> theClass, SourceClassSets ss, Set<Class<?>> visi
.filter(cls -> !seen.contains(cls))
.filter(cls -> !Void.TYPE.isAssignableFrom(cls) && !cls.isPrimitive() && !cls.equals(Object.class))
// todo smelly access ChainStrategy.getNativeTypes()
.filter(type -> !BuiltInSubjectTypeStore.getNativeTypes().contains(type) && !ss.isClassIncluded(type))
// .filter(type -> !BuiltInSubjectTypeStore.getNativeTypes().contains(type) && !ss.isClassIncluded(type))
.filter(type -> BuiltInSubjectTypeStore.hasStaticAccessThroughTruthEntryPoint(type) && !ss.isClassIncluded(type))

.collect(Collectors.toList());

if (!filtered.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public String maintain(Class<?> source, Class<?> userAndGeneratedMix) {
*/
@Override
public <T> Optional<ThreeSystem<T>> threeLayerSystem(Class<T> source, Class<T> usersMiddleClass) {
if (SourceChecking.checkSource(source, empty()))
if (SourceChecking.checkSourceShouldBeIncluded(source, empty()))
return empty();

// make parent - boilerplate access
Expand All @@ -82,7 +82,7 @@ public <T> Optional<ThreeSystem<T>> threeLayerSystem(Class<T> source, Class<T> u

@Override
public <T> Optional<ThreeSystem<T>> threeLayerSystem(Class<T> clazzUnderTest) {
if (SourceChecking.checkSource(clazzUnderTest, targetPackageName))
if (SourceChecking.checkSourceShouldBeIncluded(clazzUnderTest, targetPackageName))
return empty();

// todo make sure this doesn't override explicit shading settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class SourceChecking {

private static final FluentLogger logger = FluentLogger.forEnclosingClass();

static boolean checkSource(Class<?> source, Optional<String> targetPackage) {
static boolean checkSourceShouldBeIncluded(Class<?> source, Optional<String> targetPackage) {
if (isAnonymous(source))
return true;

Expand All @@ -27,7 +27,8 @@ static boolean checkSource(Class<?> source, Optional<String> targetPackage) {
if (isTestClass(source))
return true;

if (BuiltInSubjectTypeStore.getNativeTypes().contains(source))
//noinspection RedundantIfStatement - readability
if (BuiltInSubjectTypeStore.isANativeType(source))
return true;

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,36 @@
import com.google.common.truth.OptionalSubject;
import com.google.common.truth.Subject;
import com.google.common.truth.Truth8;
import io.stubbs.truth.generator.internal.model.ThreeSystem;
import io.stubbs.truth.generator.subjects.MyMapSubject;
import io.stubbs.truth.generator.subjects.MyStringSubject;
import io.stubbs.truth.generator.testModel.MyEmployee;
import io.stubbs.truth.generator.testModel.Person;
import lombok.SneakyThrows;
import org.junit.Test;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static com.google.common.truth.Truth.assertThat;
import static io.stubbs.truth.generator.TestModelUtils.findMethod;

/**
* Not possible to unwrap an Optional<TYPE> return type for a class with type parameters. Can only do so with a subtype
* where the type parameter has been set.
*/
public class OptionalUnwrapChainForGenericTypeArgsTest {

GeneratedSubjectTypeStore subjects = new GeneratedSubjectTypeStore(Set.of(), new BuiltInSubjectTypeStore());
public class OptionalUnwrapChainForGenericTypeArgsTest extends SubjectStoreTests {

/**
* @see MyEmployee#getTypeParamTest()
*/
@Test
public void subclass() {
var resolvedPair = testResolution(MyEmployee.class, "getTypeParamTest", String.class);
Truth8.assertThat(resolvedPair.getSubject()).isPresent();
assertThat(resolvedPair.getSubject().get().getClazz()).isEqualTo(MyStringSubject.class);
}

@Test
public void genericReturnTypeWithoutSpecialHandling() {
var resolvedPair = testResolution(MyEmployee.class, "getProjectMap", Map.class);
Truth8.assertThat(resolvedPair.getSubject()).isPresent();
assertThat(resolvedPair.getSubject().get().getClazz()).isEqualTo(MyMapSubject.class);
}

Expand All @@ -52,7 +44,6 @@ public void genericReturnTypeWithoutSpecialHandling() {
@Test
public void subclassOptional() {
var resolvedPair = testResolution(MyEmployee.class, "getTypeParamTestOptional", String.class);
Truth8.assertThat(resolvedPair.getSubject()).isPresent();
assertThat(resolvedPair.getSubject().get().getClazz()).isEqualTo(MyStringSubject.class);
}

Expand All @@ -63,7 +54,6 @@ public void subclassOptional() {
@Test
public void directClass() {
var resolvedPair = testResolution(Person.class, "getTypeParamTest", Object.class);
Truth8.assertThat(resolvedPair.getSubject()).isPresent();
assertThat(resolvedPair.getSubject().get().getClazz()).isEqualTo(Subject.class);
}

Expand All @@ -74,7 +64,6 @@ public void directClass() {
@Test
public void directClassOptional() {
var resolvedPair = testResolution(Person.class, "getTypeParamTestOptional", Object.class);
Truth8.assertThat(resolvedPair.getSubject()).isPresent();
assertThat(resolvedPair.getSubject().get().getClazz()).isEqualTo(Subject.class);
}

Expand Down Expand Up @@ -143,16 +132,4 @@ public void wildCardTypeFromSubtypeUpperBoundIdCard() {
}


private <T> GeneratedSubjectTypeStore.ResolvedPair testResolution(Class<T> classType, String methodName, Class<?> expectedReturnType) {
Method getIterationStartingPoint = findMethod(classType, methodName);

ThreeSystem<T> myEmployeeThreeSystem = new ThreeSystem<T>(classType, null, null, null);

GeneratedSubjectTypeStore.ResolvedPair resolvedPair = subjects.resolveSubjectForOptionals(myEmployeeThreeSystem, getIterationStartingPoint);

assertThat(resolvedPair.getReturnType()).isEqualTo(expectedReturnType);
return resolvedPair;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.stubbs.truth.generator.internal;

import com.google.common.truth.Truth8;
import io.stubbs.truth.generator.internal.model.ThreeSystem;

import java.lang.reflect.Method;
import java.util.Set;

import static com.google.common.truth.Truth.assertThat;
import static io.stubbs.truth.generator.TestModelUtils.findMethod;

public abstract class SubjectStoreTests {

GeneratedSubjectTypeStore subjects = new GeneratedSubjectTypeStore(Set.of(), new BuiltInSubjectTypeStore());

protected <T> GeneratedSubjectTypeStore.ResolvedPair testResolution(Class<T> classType, String methodName, Class<?> expectedReturnType) {
Method getIterationStartingPoint = findMethod(classType, methodName);

ThreeSystem<T> myEmployeeThreeSystem = new ThreeSystem<T>(classType, null, null, null);

GeneratedSubjectTypeStore.ResolvedPair resolvedPair = subjects.resolveSubjectForOptionals(myEmployeeThreeSystem, getIterationStartingPoint);


assertThat(resolvedPair.getReturnType()).isEqualTo(expectedReturnType);

Truth8.assertThat(resolvedPair.getSubject()).isPresent();

return resolvedPair;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.stubbs.truth.generator.internal;

import com.google.common.truth.LongStreamSubject;
import com.google.common.truth.OptionalDoubleSubject;
import io.stubbs.truth.generator.TestModelUtils;
import io.stubbs.truth.generator.testModel.MyEmployee;
import org.junit.Test;

import java.util.OptionalDouble;
import java.util.Set;
import java.util.stream.LongStream;

import static com.google.common.truth.Truth.assertThat;

public class Truth8Support extends SubjectStoreTests {

GeneratedSubjectTypeStore subjects = new GeneratedSubjectTypeStore(Set.of(), new BuiltInSubjectTypeStore());

@Test
public void doubleStream() {
// for reference
MyEmployee employee = TestModelUtils.createEmployee();
LongStream myLongStream = employee.getMyLongStream();

//
var res = super.testResolution(MyEmployee.class, "getMyLongStream", LongStream.class);
assertThat(res.getSubject().get().getClazz()).isEqualTo(LongStreamSubject.class);
}

@Test
public void optionalDouble() {
// for reference
MyEmployee employee = TestModelUtils.createEmployee();
OptionalDouble myOptionalDouble = employee.getMyOptionalDouble();

//
var res = super.testResolution(MyEmployee.class, "getMyOptionalDouble", OptionalDouble.class);
assertThat(res.getSubject().get().getClazz()).isEqualTo(OptionalDoubleSubject.class);
}
}
Loading