Skip to content

Instantly share code, notes, and snippets.

@MarcinMoskala
Created February 10, 2019 17:12
Show Gist options
  • Save MarcinMoskala/b7431cd725143739980ed004f50dfda4 to your computer and use it in GitHub Desktop.
Save MarcinMoskala/b7431cd725143739980ed004f50dfda4 to your computer and use it in GitHub Desktop.
import org.junit.jupiter.api.Test
import kotlin.random.Random
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.test.assertEquals
import kotlin.test.assertTrue
// Take getKType from https://gist.github.com/udalov/bb6f398c2e643ee69586356fdd67e9b1
inline fun <reified T : Any> makeRandomInstance(
random: Random = Random,
config: MakeRandomInstanceConfig = MakeRandomInstanceConfig()
): T {
val producer = RandomInstanceProducer(random, config)
return producer.makeRandomInstance(T::class, getKType<T>()) as T
}
class NoUsableConstructor : Error()
class MakeRandomInstanceConfig(
var possibleCollectionSizes: IntRange = 1..5,
var possibleStringSizes: IntRange = 1..10,
var any: Any = "Anything"
)
class RandomInstanceProducer(
private val random: Random,
private val config: MakeRandomInstanceConfig
) {
private fun makeRandomInstanceForParam(paramType: KType, classRef: KClass<*>, type: KType): Any? {
val classifier = paramType.classifier
return when (classifier) {
is KClass<*> -> makeRandomInstance(classifier, paramType)
is KTypeParameter -> {
val typeParameterName = classifier.name
val typeParameterId = classRef.typeParameters.indexOfFirst { it.name == typeParameterName }
val parameterType = type.arguments[typeParameterId].type ?: getKType<Any>()
makeRandomInstance(parameterType.classifier as KClass<*>, parameterType)
}
else -> throw Error("Type of the classifier $classifier is not supported")
}
}
fun makeRandomInstance(classRef: KClass<*>, type: KType): Any? {
val primitive = makeStandardInstanceOrNull(classRef, type)
if (primitive != null) {
return primitive
}
val constructors = classRef.constructors.shuffled(random)
for (constructor in constructors) {
try {
val arguments = constructor.parameters
.map { makeRandomInstanceForParam(it.type, classRef, type) }
.toTypedArray()
return constructor.call(*arguments)
} catch (e: Throwable) {
e.printStackTrace()
// no-op. We catch any possible error here that might occur during class creation
}
}
throw NoUsableConstructor()
}
@Suppress("IMPLICIT_CAST_TO_ANY")
private fun makeStandardInstanceOrNull(classRef: KClass<*>, type: KType) = when (classRef) {
Any::class -> config.any
Int::class -> random.nextInt()
Long::class -> random.nextLong()
Double::class -> random.nextDouble()
Float::class -> random.nextFloat()
Char::class -> makeRandomChar(random)
String::class -> makeRandomString(random)
List::class, Collection::class -> makeRandomList(classRef, type)
Map::class -> makeRandomMap(classRef, type)
else -> null
}
private fun makeRandomList(classRef: KClass<*>, type: KType): List<Any?> {
val numOfElements = random.nextInt(config.possibleCollectionSizes.start, config.possibleCollectionSizes.endInclusive + 1)
val elemType = type.arguments[0].type!!
return (1..numOfElements)
.map { makeRandomInstanceForParam(elemType, classRef, type) }
}
private fun makeRandomMap(classRef: KClass<*>, type: KType): Map<Any?, Any?> {
val numOfElements = random.nextInt(config.possibleCollectionSizes.start, config.possibleCollectionSizes.endInclusive + 1)
val keyType = type.arguments[0].type!!
val valType = type.arguments[1].type!!
val keys = (1..numOfElements)
.map { makeRandomInstanceForParam(keyType, classRef, type) }
val values = (1..numOfElements)
.map { makeRandomInstanceForParam(valType, classRef, type) }
return keys.zip(values).toMap()
}
private fun makeRandomChar(random: Random) = ('A'..'z').random(random)
private fun makeRandomString(random: Random) =
(1..random.nextInt(config.possibleStringSizes.start, config.possibleStringSizes.endInclusive + 1))
.map { makeRandomChar(random) }
.joinToString(separator = "") { "$it" }
}
@Suppress("USELESS_IS_CHECK")
class MakeRandomInstanceTest {
class A {
override fun toString(): String = "A"
}
data class B(val a: A)
data class C(val b1: B, val c: Char, val b2: B, val str: String, val l: Long)
class D {
lateinit var a: A
constructor() {
throw Error("Do not use this one")
}
constructor(a: A) {
this.a = a
}
override fun toString() = "D(a=$a)"
}
data class E(val b: B, val map: Map<Long, String>, val l: Long)
data class F(val l: List<E>, val e: E)
data class L(val ints: List<Int>)
class P {
private constructor()
}
@Test
fun `Creates single instance using an empty constructor`() {
val a: A = makeRandomInstance()
assertTrue(a is A)
assertTrue("A" in a.toString())
}
@Test
fun `Throws NoUsableConstructor error if there is no constructor that could be used`() {
try {
makeRandomInstance<P>()
error("makeRandomInstance should throw NoUsableConstructor error")
} catch (e: NoUsableConstructor) {
// no-op
}
}
private fun catchError(function: () -> Unit): Throwable? = try {
function()
null
} catch (throwable: Throwable) {
throwable
}
@Test
fun `Creates using constructor`() {
val b: B = makeRandomInstance()
assertTrue(b is B)
assertEquals("B(a=A)", b.toString())
}
@Test
fun `Skipps constructors that cannot be used`() {
val d: D = makeRandomInstance()
assertTrue(d is D)
assertEquals("D(a=A)", d.toString())
}
@Test
fun `Creates primitives`() {
assertTrue(makeRandomInstance<Int>() is Int)
assertTrue(makeRandomInstance<Long>() is Long)
assertTrue(makeRandomInstance<Double>() is Double)
assertTrue(makeRandomInstance<Float>() is Float)
assertTrue(makeRandomInstance<Char>() is Char)
assertTrue(makeRandomInstance<String>() is String)
//... etc. Don't forget about arrays
}
@Test
fun `Creates an instance using constructor with primitives and standard types`() {
val b: B = makeRandomInstance()
assertTrue(b is B)
assertTrue("B(a=A)" in b.toString())
val c: C = makeRandomInstance()
assertTrue(c is C)
assertTrue(c.toString().matches("C\\(b1=B\\(a=A\\), c=[A-z], b2=B\\(a=A\\), str=[A-z]*, l=-?\\d*\\)".toRegex()), "It is $c")
}
@Test
fun `Creates collections`() {
val ints = makeRandomInstance<List<Int>>()
assertTrue(ints is List<Int>)
assertTrue(ints.toString().startsWith("["))
assertTrue(ints.toString().endsWith("]"))
val map = makeRandomInstance<Map<Long, String>>()
assertTrue(map is Map<Long, String>)
assertTrue(map.toString().startsWith("{"))
assertTrue(map.toString().endsWith("}"))
assertTrue(makeRandomInstance<Collection<A>>() is Collection<A>)
}
@Test
fun `Creates an instance using constructor with collections, primitives and standard types`() {
val b: L = makeRandomInstance()
assertTrue(b is L)
assertTrue(b.ints is List<Int>)
assertTrue(b.ints.firstOrNull() is Int?)
val e: E = makeRandomInstance()
assertTrue(e is E)
assertTrue(e.map.all { (k, v) -> k is Long && v is String })
assertTrue(e.toString().startsWith("E(b=B(a=A), map={"))
}
class GT<T> {
var t: T? = null
}
class GA<T>(var t: T)
class GAA<T1, T2>(val t1: T1, val t2: T2)
class GTA<T1, T2>(val t2: T2)
@Test
fun `Generic classes are supported`() {
val gt1 = makeRandomInstance<GT<Int>>()
gt1.t = 1
assertEquals(1, gt1.t)
gt1.t = 10
val gt2 = makeRandomInstance<GT<Long>>()
gt2.t = 1L
assertEquals(1L, gt2.t)
gt2.t = 10L
val gtRecursive = makeRandomInstance<GT<GT<Int>>>()
gtRecursive.t = gt1
assertEquals(gt1, gtRecursive.t)
val ga1: GA<Int> = makeRandomInstance()
assertTrue(ga1.t is Int)
val ga2: GA<String> = makeRandomInstance()
assertTrue(ga2.t is String)
val gaa1: GAA<Int, String> = makeRandomInstance()
assertTrue(gaa1.t1 is Int)
assertTrue(gaa1.t2 is String)
val gaa2: GAA<Long, List<Int>> = makeRandomInstance()
assertTrue(gaa2.t1 is Long)
assertTrue(gaa2.t2 is List<Int>)
val gta: GTA<Long, String> = makeRandomInstance()
gta.t2.length
val gaaga: GAA<Long, GA<GT<Int>>> = makeRandomInstance()
assertTrue(gaaga.t1 is Long)
assertTrue(gaaga.t2 is GA<GT<Int>>)
val gggg: GA<GA<GA<Int>>> = makeRandomInstance()
gggg.t.t.t = 10
assertEquals(10, gggg.t.t.t)
gggg.t.t = GA(20)
assertEquals(20, gggg.t.t.t)
}
@Test
fun `When user expects empty collections, both Map and List are empty`() {
val config = MakeRandomInstanceConfig(possibleCollectionSizes = 0..0)
repeat(10) {
assertEquals(emptyList(), makeRandomInstance<List<Int>>(config = config))
assertEquals(emptyList(), makeRandomInstance<List<List<Int>>>(config = config))
assertEquals(emptyMap(), makeRandomInstance<Map<Int, String>>(config = config))
}
}
@Test
fun `When user expects concrete collection size, both Map and List are of this size`() {
val config = MakeRandomInstanceConfig(possibleCollectionSizes = 5..5)
repeat(10) {
assertEquals(5, makeRandomInstance<List<Int>>(config = config).size)
assertEquals(5, makeRandomInstance<List<List<Int>>>(config = config).size)
assertEquals(5, makeRandomInstance<Map<Int, String>>(config = config).size)
}
}
@Test
fun `When user expects concrete String length, all Strings have this length`() {
val config = MakeRandomInstanceConfig(possibleStringSizes = 5..5, possibleCollectionSizes = 2..2)
repeat(10) {
assertEquals(5, makeRandomInstance<String>(config = config).length)
assertEquals(5, makeRandomInstance<List<String>>(config = config)[0].length)
assertEquals(5, makeRandomInstance<List<List<String>>>(config = config)[1][1].length)
}
}
@Test
fun `Object set in config as Any, is always returned when we expect Any`() {
val any = object {}
val config = MakeRandomInstanceConfig(any = any)
repeat(10) {
assertEquals(any, makeRandomInstance<Any>(config = config))
assertEquals(any, makeRandomInstance<GA<Any>>(config = config).t)
assertEquals(any, makeRandomInstance<GA<GA<Any>>>(config = config).t.t)
}
}
@Test
fun `Check expected random values`() {
val random = Random(12345)
assertEquals("A", makeRandomInstance<A>(random).toString())
assertEquals("B(a=A)", makeRandomInstance<B>(random).toString())
assertEquals("C(b1=B(a=A), c=Y, b2=B(a=A), str=yS, l=-6367288518484839692)", makeRandomInstance<C>(random).toString())
assertEquals("D(a=A)", makeRandomInstance<D>(random).toString())
assertEquals("E(b=B(a=A), map={-6428220448289816081=voWGUkC\\, 7288696731122253832=kp]U, -4359497035184897174=Nc`tCa, -8481730907691591520=M}, l=1553348274986458979)", makeRandomInstance<E>(random).toString())
assertEquals("F(l=[E(b=B(a=A), map={-6065142614942521822=t, 416890183638600344=p_JLM]iD^y, 4288163720945964501=z`AGmw}, l=5098165797873145605), E(b=B(a=A), map={-1045531387234036165=SYMlUY, 943176485828979=bkcU, 7917721219055033990=xl^\\dB, -3835092262954011188=lOpa^clPX}, l=870933613633965720), E(b=B(a=A), map={3682447410090233778=LfX[Em, -6203180773228722909=KeloXcypXg, -3662781481041013612=]lILXf, 8884333428377293996=e, 827637008512869092=Zwcu}, l=1759343391314632900), E(b=B(a=A), map={3072701447898829435=GYDLUYDSdn}, l=-6214529434004727598), E(b=B(a=A), map={-5540839068437135337=HTxnLiz, 8783042796281279363=vGYAp`, 5347169406213203755=bOpO_P, -4827221593046365475=vkn}, l=1667205364588981005)], e=E(b=B(a=A), map={-826488732485778636=SAv, 398313030478269938=sDFgHE, 7535000932527017313=jboPScOh, 321039882229839452=bg}, l=-2421217787312800394))", makeRandomInstance<F>(random).toString())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment