-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
213 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 0 additions & 11 deletions
11
core/src/main/kotlin/fr/xgouchet/elmyr/ForgeryFactoryMissingException.kt
This file was deleted.
Oops, something went wrong.
74 changes: 74 additions & 0 deletions
74
core/src/main/kotlin/fr/xgouchet/elmyr/ReflexiveFactory.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package fr.xgouchet.elmyr | ||
|
||
import kotlin.reflect.KClass | ||
import kotlin.reflect.KType | ||
import kotlin.reflect.KTypeProjection | ||
import kotlin.reflect.full.valueParameters | ||
|
||
internal class ReflexiveFactory( | ||
private val forge: Forge | ||
) { | ||
|
||
fun <T : Any> getForgery(kClass: KClass<T>): T { | ||
if (kClass.isData) { | ||
return getDataClassForgery(kClass) | ||
} else { | ||
throw ReflexiveForgeryFactoryException(kClass.java, "only data classes are supported") | ||
} | ||
} | ||
|
||
private fun <T : Any> getDataClassForgery(kClass: KClass<T>): T { | ||
val constructor = kClass.constructors.first() | ||
val parameters = constructor.valueParameters | ||
val arguments = Array<Any?>(parameters.size) { null } | ||
|
||
parameters.forEachIndexed { idx, param -> | ||
arguments[idx] = forgeType(param.type, kClass.java) | ||
} | ||
return constructor.call(*arguments) | ||
} | ||
|
||
private fun forgeType(type: KType, fromClass: Class<*>): Any? { | ||
val typeClassifier = type.classifier | ||
|
||
return when (typeClassifier) { | ||
Boolean::class -> forge.aBool() | ||
Int::class -> forge.anInt() | ||
Long::class -> forge.aLong() | ||
Float::class -> forge.aFloat() | ||
Double::class -> forge.aDouble() | ||
String::class -> forge.aString() | ||
java.util.List::class -> forgeList(type.arguments, fromClass) | ||
java.util.Map::class -> forgeMap(type.arguments, fromClass) | ||
java.util.Set::class -> forgeList(type.arguments, fromClass).toSet() | ||
is KClass<*> -> getForgery(typeClassifier) | ||
else -> throw ReflexiveForgeryFactoryException(fromClass, "Unknown parameter type $type") | ||
} | ||
} | ||
|
||
private fun forgeList(arguments: List<KTypeProjection>, fromClass: Class<*>): List<Any?> { | ||
if (arguments.size != 1) { | ||
throw ReflexiveForgeryFactoryException(fromClass, "wrong number of type arguments for List") | ||
} | ||
|
||
val itemType = arguments.first().type | ||
if (itemType != null) { | ||
return forge.aList { forgeType(itemType, fromClass) } | ||
} else { | ||
throw ReflexiveForgeryFactoryException(fromClass, "unknown type argument for List") | ||
} | ||
} | ||
|
||
private fun forgeMap(arguments: List<KTypeProjection>, fromClass: Class<*>): Map<Any?, Any?> { | ||
if (arguments.size != 2) { | ||
throw ReflexiveForgeryFactoryException(fromClass, "wrong number of type arguments for Map") | ||
} | ||
val keyType = arguments[0].type | ||
val valueType = arguments[1].type | ||
if (keyType is KType && valueType is KType) { | ||
return forge.aMap { forgeType(keyType, fromClass) to forgeType(valueType, fromClass) } | ||
} else { | ||
throw ReflexiveForgeryFactoryException(fromClass, "unknown key or value type argument for Map") | ||
} | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
core/src/test/kotlin/fr/xgouchet/elmyr/ReflexiveFactorySpek.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package fr.xgouchet.elmyr | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.spekframework.spek2.Spek | ||
import org.spekframework.spek2.style.specification.describe | ||
|
||
class ReflexiveFactorySpek : Spek({ | ||
|
||
describe("A forge") { | ||
val forge = Forge() | ||
var seed: Long | ||
|
||
beforeEachTest { | ||
seed = Forge.seed() | ||
forge.seed = seed | ||
} | ||
|
||
context("forging unknown data class ") { | ||
|
||
it("cannot forge a non data class") { | ||
try { | ||
forge.getForgery<NotADataClass>() | ||
throw AssertionError("Should fail here") | ||
} catch (e: ForgeryException) { | ||
// Nothing to do here | ||
} | ||
} | ||
|
||
it("forges data class instance with primitive fields") { | ||
val withPrimitiveFields = forge.getForgery<WithPrimitiveFields>() | ||
|
||
assertThat(withPrimitiveFields.anInt).isNotZero() | ||
assertThat(withPrimitiveFields.aLong).isNotZero() | ||
assertThat(withPrimitiveFields.aFloat).isNotZero() | ||
assertThat(withPrimitiveFields.aDouble).isNotZero() | ||
assertThat(withPrimitiveFields.aString).isNotEmpty() | ||
} | ||
|
||
it("forges data class instance with data class fields") { | ||
val withDataClassField = forge.getForgery<WithDataClassField>() | ||
|
||
val withPrimitiveFields = withDataClassField.field | ||
assertThat(withPrimitiveFields.anInt).isNotZero() | ||
assertThat(withPrimitiveFields.aLong).isNotZero() | ||
assertThat(withPrimitiveFields.aFloat).isNotZero() | ||
assertThat(withPrimitiveFields.aDouble).isNotZero() | ||
assertThat(withPrimitiveFields.aString).isNotEmpty() | ||
} | ||
|
||
it("forges data class instance with collection fields") { | ||
val withCollectionFields = forge.getForgery<WithCollectionFields>() | ||
|
||
assertThat(withCollectionFields.floatList).isNotEmpty() | ||
assertThat(withCollectionFields.stringToLongMap).isNotEmpty() | ||
assertThat(withCollectionFields.dataClassSet).isNotEmpty() | ||
} | ||
} | ||
} | ||
}) | ||
|
||
class NotADataClass(val i: Int) | ||
|
||
data class WithPrimitiveFields( | ||
val aBool: Boolean, | ||
val anInt: Int, | ||
val aLong: Long, | ||
val aFloat: Float, | ||
val aDouble: Double, | ||
val aString: String | ||
) | ||
|
||
data class WithDataClassField(val field: WithPrimitiveFields) | ||
|
||
data class WithCollectionFields( | ||
val floatList: List<Float>, | ||
val stringToLongMap: Map<String, Long>, | ||
val dataClassSet: Set<WithPrimitiveFields> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters