Skip to content

Commit

Permalink
add unit tests for ConversionExprFactory implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
sigpwned committed Jan 13, 2025
1 parent ec73cc8 commit f8d9847
Show file tree
Hide file tree
Showing 14 changed files with 1,187 additions and 39 deletions.
21 changes: 18 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,18 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
<configuration>
<!-- The below is required for testing annotation
processors -->
<!-- using com.google.testing.compile:compile-testing. -->
<!--
-Xshare:off is to suppress some warnings from
Mockito when mocking Object methods
-->
<!--
The "add exports" flags below below are required for
testing annotation processors using
com.google.testing.compile:compile-testing in
Java 9+.
-->
<argLine>
-Xshare:off
--add-exports
jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-opens
Expand Down Expand Up @@ -226,6 +234,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-bom</artifactId>
<version>5.15.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.dagger</groupId>
<artifactId>dagger</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions rapier-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,10 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import static java.util.Objects.requireNonNull;
import java.util.List;
import java.util.Optional;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
Expand All @@ -40,29 +39,29 @@ public ElementwiseListConversionExprFactory(Types types,
}

@Override
@SuppressWarnings("unused")
public Optional<String> generateConversionExpr(TypeMirror targetType, String sourceValue) {
if (targetType.getKind() != TypeKind.DECLARED)
return Optional.empty();
final TypeElement targetElement = (TypeElement) getTypes().asElement(targetType);
final DeclaredType targetDeclaredType = (DeclaredType) targetType;

// Get type arguments
final TypeMirror targetErasedType = getTypes().erasure(targetType);
if (!targetErasedType.toString().equals("java.util.List"))
return Optional.empty();

// Get the type arguments of the target type. If there are no type arguments, then this is a
// raw List type, and we can't see the element type, so just return.
List<? extends TypeMirror> typeArguments = targetDeclaredType.getTypeArguments();

// Ensure there's exactly one type argument. There might be zero, but there really, really
// shouldn't be more than 1.
if (typeArguments.size() != 1)
if (typeArguments.size() == 0)
return Optional.empty();

// Get the first type argument
final TypeMirror targetTypeArgument = typeArguments.get(0);
if (targetTypeArgument.getKind() != TypeKind.DECLARED)
// Get the type argument. If it's not an exact declared type, then we're done.
final TypeMirror targeElementType = typeArguments.get(0);
if (targeElementType.getKind() != TypeKind.DECLARED)
return Optional.empty();

// Generate conversion expression for each element
final Optional<String> maybeElementConversionExpr =
getElementConversionExprFactory().generateConversionExpr(targetTypeArgument, "element");
getElementConversionExprFactory().generateConversionExpr(targeElementType, "element");
if (maybeElementConversionExpr.isEmpty())
return Optional.empty();
final String elementConversionExpr = maybeElementConversionExpr.orElseThrow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@

import static java.util.Objects.requireNonNull;
import java.util.Optional;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import rapier.core.ConversionExprFactory;
Expand All @@ -38,16 +35,9 @@ public IdentityConversionExprFactory(Types types, TypeMirror sourceType) {
}

@Override
@SuppressWarnings("unused")
public Optional<String> generateConversionExpr(TypeMirror targetType, String sourceValue) {
if (targetType.getKind() != TypeKind.DECLARED)
return Optional.empty();
final TypeElement targetElement = (TypeElement) getTypes().asElement(targetType);
final DeclaredType targetDeclaredType = (DeclaredType) targetType;

if (getTypes().isSameType(targetType, getSourceType()))
return Optional.of(sourceValue);

return Optional.empty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@

import static java.util.Objects.requireNonNull;
import java.util.Optional;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import rapier.core.ConversionExprFactory;
Expand All @@ -36,20 +33,15 @@ public StringToCharacterConversionExprFactory(Types types) {
}

@Override
@SuppressWarnings("unused")
public Optional<String> generateConversionExpr(TypeMirror targetType, String sourceValue) {
if (targetType.getKind() != TypeKind.DECLARED)
return Optional.empty();
final TypeElement targetElement = (TypeElement) getTypes().asElement(targetType);
final DeclaredType targetDeclaredType = (DeclaredType) targetType;

if (!targetDeclaredType.toString().equals("java.lang.Character"))
if (!targetType.toString().equals("java.lang.Character"))
return Optional.empty();

return Optional.of("Optional.of(" + sourceValue
+ ").filter(s -> !s.isEmpty()).map(s -> Character.valueOf(s.charAt(0))).orElseThrow(() -> new IllegalStateException(\"Cannot convert empty string to char\"))");
+ ").map(s -> s.isEmpty() ? null : s.charAt(0)).orElseThrow(() -> new IllegalStateException(\"Cannot convert empty string to char\"))");
}

@SuppressWarnings("unused")
private Types getTypes() {
return types;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public StringToPrimitiveConversionExprFactory(Types types) {

@Override
public Optional<String> generateConversionExpr(TypeMirror targetType, String sourceValue) {
if (!targetType.getKind().isPrimitive())
return Optional.empty();
switch (targetType.getKind()) {
case BOOLEAN:
return Optional.of("Boolean.parseBoolean(" + sourceValue + ")");
Expand All @@ -46,13 +48,14 @@ public Optional<String> generateConversionExpr(TypeMirror targetType, String sou
case LONG:
return Optional.of("Long.parseLong(" + sourceValue + ")");
case CHAR:
return Optional.of(sourceValue + ".charAt(0)");
return Optional.of("Optional.of(" + sourceValue
+ ").map(s -> s.isEmpty() ? null : s.charAt(0)).orElseThrow(() -> new IllegalStateException(\"Cannot convert empty string to char\"))");
case FLOAT:
return Optional.of("Float.parseFloat(" + sourceValue + ")");
case DOUBLE:
return Optional.of("Double.parseDouble(" + sourceValue + ")");
default:
return Optional.empty();
throw new AssertionError("Unexpected primitive type: " + targetType);
}
}

Expand Down
2 changes: 1 addition & 1 deletion rapier-core/src/test/java/rapier/core/RapierTestBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ private static String toQualifiedClassName(JavaFileObject file) {
private static final Pattern PACKAGE_DECLARATION_PATTERN =
Pattern.compile("^package\\s+(\\S+)\\s*;", Pattern.MULTILINE);
private static final Pattern CLASS_DECLARATION_PATTERN = Pattern.compile(
"^public\\s+(?:class|interface)\\s+(\\S+)\\s*(?:extends\\s+\\S+)?\\s*(?:implements\\s+\\S+(?:\\s*,\\s*\\S+)*)?\\s*\\{",
"^public\\s+(?:abstract\\s+)?(?:class|@?interface)\\s+(\\S+)\\s*(?:extends\\s+\\S+)?\\s*(?:implements\\s+\\S+(?:\\s*,\\s*\\S+)*)?\\s*\\{",
Pattern.MULTILINE);

public JavaFileObject prepareSourceFile(String sourceCode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*-
* =================================LICENSE_START==================================
* rapier-core
* ====================================SECTION=====================================
* Copyright (C) 2024 - 2025 Andy Boothe
* ====================================SECTION=====================================
* Licensed 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.
* ==================================LICENSE_END===================================
*/
package rapier.core.conversion.expr;

import static java.util.Collections.emptyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.junit.jupiter.api.Test;
import rapier.core.ConversionExprFactory;

public class ElementwiseListConversionExprFactoryTest {
@Test
@SuppressWarnings({"unchecked", "rawtypes"})
public void givenFullyReifiedListTargetType_whenComputeConversionExpr_thenGetExpectedConversionExpr() {
final Types types = mock(Types.class);

final TypeMirror listType = mock(TypeMirror.class);
when(listType.getKind()).thenReturn(TypeKind.DECLARED);
when(listType.toString()).thenReturn("java.util.List");

final TypeMirror targetTypeAsTypeMirror =
mock(TypeMirror.class, withSettings().extraInterfaces(DeclaredType.class));
when(targetTypeAsTypeMirror.getKind()).thenReturn(TypeKind.DECLARED);
when(targetTypeAsTypeMirror.toString()).thenReturn("java.util.List<java.lang.Integer>");
when(types.erasure(targetTypeAsTypeMirror)).thenReturn(listType);

final TypeMirror targetTypeTypeArgument = mock(TypeMirror.class);
when(targetTypeTypeArgument.getKind()).thenReturn(TypeKind.DECLARED);
when(targetTypeTypeArgument.toString()).thenReturn("java.lang.Integer");

final List<TypeMirror> targetTypeTypeArguments = new ArrayList<>();
targetTypeTypeArguments.add(targetTypeTypeArgument);

final DeclaredType targetTypeAsDeclaredType = (DeclaredType) targetTypeAsTypeMirror;
when(targetTypeAsDeclaredType.getTypeArguments()).thenReturn((List) targetTypeTypeArguments);

final ConversionExprFactory innerConversionExprFactory = mock(ConversionExprFactory.class);
when(innerConversionExprFactory.generateConversionExpr(targetTypeTypeArgument, "element"))
.thenReturn(Optional.of("Integer.valueOf(element)"));

final ElementwiseListConversionExprFactory unit =
new ElementwiseListConversionExprFactory(types, innerConversionExprFactory);

final String conversionExpr =
unit.generateConversionExpr(targetTypeAsTypeMirror, "value").orElse(null);

assertEquals(
"value.stream().map(element -> Integer.valueOf(element)).collect(java.util.stream.Collectors.toList())",
conversionExpr);
}

@Test
public void givenRawListTargetType_whenComputeConversionExpr_thenGetExpectedConversionExpr() {
final Types types = mock(Types.class);

final TypeMirror targetTypeAsTypeMirror =
mock(TypeMirror.class, withSettings().extraInterfaces(DeclaredType.class));
when(targetTypeAsTypeMirror.getKind()).thenReturn(TypeKind.DECLARED);
when(targetTypeAsTypeMirror.toString()).thenReturn("java.util.List");
when(types.erasure(targetTypeAsTypeMirror)).thenReturn(targetTypeAsTypeMirror);

final DeclaredType targetTypeAsDeclaredType = (DeclaredType) targetTypeAsTypeMirror;
when(targetTypeAsDeclaredType.getTypeArguments()).thenReturn(emptyList());

final ConversionExprFactory innerConversionExprFactory = mock(ConversionExprFactory.class);

final ElementwiseListConversionExprFactory unit =
new ElementwiseListConversionExprFactory(types, innerConversionExprFactory);

final String conversionExpr =
unit.generateConversionExpr(targetTypeAsTypeMirror, "value").orElse(null);

assertEquals(null, conversionExpr);

verifyNoInteractions(innerConversionExprFactory);
}

@Test
public void givenNonListTargetType_whenComputeConversionExpr_thenGetNoConversionExpr() {
final Types types = mock(Types.class);

final TypeMirror mapType = mock(TypeMirror.class);
when(mapType.getKind()).thenReturn(TypeKind.DECLARED);
when(mapType.toString()).thenReturn("java.util.Map");

final TypeMirror targetTypeAsTypeMirror =
mock(TypeMirror.class, withSettings().extraInterfaces(DeclaredType.class));
when(targetTypeAsTypeMirror.getKind()).thenReturn(TypeKind.DECLARED);
when(targetTypeAsTypeMirror.toString())
.thenReturn("java.util.Map<java.lang.String,java.lang.Integer>");
when(types.erasure(targetTypeAsTypeMirror)).thenReturn(mapType);

final ConversionExprFactory innerConversionExprFactory = mock(ConversionExprFactory.class);

final ElementwiseListConversionExprFactory unit =
new ElementwiseListConversionExprFactory(types, innerConversionExprFactory);

final String conversionExpr =
unit.generateConversionExpr(targetTypeAsTypeMirror, "value").orElse(null);

assertEquals(null, conversionExpr);

verifyNoInteractions(innerConversionExprFactory);
}
}
Loading

0 comments on commit f8d9847

Please sign in to comment.