From 6749aa84148677834de20f199f3939bc39b68820 Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Wed, 3 May 2023 10:40:07 +0100 Subject: [PATCH] Ensure Arrays of primitives are correctly generated. (#97) Works around issues in Kotlins type system. See https://youtrack.jetbrains.com/issue/KT-52170/Reflection-typeOfArrayLong-gives-classifier-LongArray Fixes #92 --- .../resolver/ArrayKTypeResolver.kt | 29 +- .../com/appmattus/kotlinfixture/ArrayTest.kt | 353 ++++++++++++++++++ .../appmattus/kotlinfixture/ComparisonTest.kt | 38 +- .../resolver/ArrayKTypeResolverTest.kt | 9 +- 4 files changed, 416 insertions(+), 13 deletions(-) create mode 100644 fixture/src/test/kotlin/com/appmattus/kotlinfixture/ArrayTest.kt diff --git a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolver.kt b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolver.kt index 1ac21041..1f2987d3 100644 --- a/fixture/src/main/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolver.kt +++ b/fixture/src/main/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolver.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 Appmattus Limited + * Copyright 2020-2023 Appmattus Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package com.appmattus.kotlinfixture.resolver import com.appmattus.kotlinfixture.Context import com.appmattus.kotlinfixture.Unresolved import com.appmattus.kotlinfixture.Unresolved.Companion.createUnresolved +import com.appmattus.kotlinfixture.typeOf import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.full.starProjectedType @@ -26,7 +27,20 @@ import kotlin.reflect.full.starProjectedType internal class ArrayKTypeResolver : Resolver { @Suppress("ReturnCount") - override fun resolve(context: Context, obj: Any): Any? { + override fun resolve(context: Context, obj: Any): Any { + // Special handling for primitive arrays as Kotlin's KType seems to see Array as IntArray etc when looking at the classifier + // See https://youtrack.jetbrains.com/issue/KT-52170/Reflection-typeOfArrayLong-gives-classifier-LongArray + when (obj) { + BooleanArrayKType -> return (context.resolve(BooleanArray::class) as BooleanArray).toTypedArray() + ByteArrayKType -> return (context.resolve(ByteArray::class) as ByteArray).toTypedArray() + DoubleArrayKType -> return (context.resolve(DoubleArray::class) as DoubleArray).toTypedArray() + FloatArrayKType -> return (context.resolve(FloatArray::class) as FloatArray).toTypedArray() + IntArrayKType -> return (context.resolve(IntArray::class) as IntArray).toTypedArray() + LongArrayKType -> return (context.resolve(LongArray::class) as LongArray).toTypedArray() + ShortArrayKType -> return (context.resolve(ShortArray::class) as ShortArray).toTypedArray() + CharArrayKType -> return (context.resolve(CharArray::class) as CharArray).toTypedArray() + } + if (obj is KType && obj.classifier?.starProjectedType == Array::class.starProjectedType) { val size = context.configuration.repeatCount() @@ -48,4 +62,15 @@ internal class ArrayKTypeResolver : Resolver { return Unresolved.Unhandled } + + companion object { + private val BooleanArrayKType = typeOf>() + private val ByteArrayKType = typeOf>() + private val DoubleArrayKType = typeOf>() + private val FloatArrayKType = typeOf>() + private val IntArrayKType = typeOf>() + private val LongArrayKType = typeOf>() + private val ShortArrayKType = typeOf>() + private val CharArrayKType = typeOf>() + } } diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/ArrayTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/ArrayTest.kt new file mode 100644 index 00000000..8d43630f --- /dev/null +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/ArrayTest.kt @@ -0,0 +1,353 @@ +/* + * Copyright 2023 Appmattus Limited + * + * 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. + */ + +package com.appmattus.kotlinfixture + +import com.appmattus.kotlinfixture.config.TestGenerator.fixture +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * More robust testing around Array fixtures as we now have special handling for arrays of primitive types + * See https://youtrack.jetbrains.com/issue/KT-52170/Reflection-typeOfArrayLong-gives-classifier-LongArray + */ +@OptIn(ExperimentalUnsignedTypes::class) +class ArrayTest { + + @Test + fun arrayBoolean() { + assertIsRandom { + fixture>().onEach { + assertTrue { Boolean::class.isInstance(it) } + } + } + } + + @Test + fun arrayByte() { + assertIsRandom { + fixture>().onEach { + assertTrue { Byte::class.isInstance(it) } + } + } + } + + @Test + fun arrayDouble() { + assertIsRandom { + fixture>().onEach { + assertTrue { Double::class.isInstance(it) } + } + } + } + + @Test + fun arrayFloat() { + assertIsRandom { + fixture>().onEach { + assertTrue { Float::class.isInstance(it) } + } + } + } + + @Test + fun arrayInt() { + assertIsRandom { + fixture>().onEach { + assertTrue { Int::class.isInstance(it) } + } + } + } + + @Test + fun arrayLong() { + assertIsRandom { + fixture>().onEach { + assertTrue { Long::class.isInstance(it) } + } + } + } + + @Test + fun arrayShort() { + assertIsRandom { + fixture>().onEach { + assertTrue { Short::class.isInstance(it) } + } + } + } + + @Test + fun arrayChar() { + assertIsRandom { + fixture>().onEach { + assertTrue { Char::class.isInstance(it) } + } + } + } + + @Test + fun arrayUByte() { + assertIsRandom { + fixture>().onEach { + assertTrue { UByte::class.isInstance(it) } + } + } + } + + @Test + fun arrayUInt() { + assertIsRandom { + fixture>().onEach { + assertTrue { UInt::class.isInstance(it) } + } + } + } + + @Test + fun arrayULong() { + assertIsRandom { + fixture>().onEach { + assertTrue { ULong::class.isInstance(it) } + } + } + } + + @Test + fun arrayUShort() { + assertIsRandom { + fixture>().onEach { + assertTrue { UShort::class.isInstance(it) } + } + } + } + + @Test + fun booleanArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Boolean::class.isInstance(it) } + } + } + } + + @Test + fun byteArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Byte::class.isInstance(it) } + } + } + } + + @Test + fun doubleArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Double::class.isInstance(it) } + } + } + } + + @Test + fun floatArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Float::class.isInstance(it) } + } + } + } + + @Test + fun intArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Int::class.isInstance(it) } + } + } + } + + @Test + fun longArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Long::class.isInstance(it) } + } + } + } + + @Test + fun shortArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Short::class.isInstance(it) } + } + } + } + + @Test + fun charArray() { + assertIsRandom { + fixture().onEach { + assertTrue { Char::class.isInstance(it) } + } + } + } + + @Test + fun uByteArray() { + assertIsRandom { + fixture().onEach { + assertTrue { UByte::class.isInstance(it) } + } + } + } + + @Test + fun uIntArray() { + assertIsRandom { + fixture().onEach { + assertTrue { UInt::class.isInstance(it) } + } + } + } + + @Test + fun uLongArray() { + assertIsRandom { + fixture().onEach { + assertTrue { ULong::class.isInstance(it) } + } + } + } + + @Test + fun uShortArray() { + assertIsRandom { + fixture().onEach { + assertTrue { UShort::class.isInstance(it) } + } + } + } + + @Test + fun arrayBooleanNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Boolean::class.isInstance(it) } + } + } + } + + @Test + fun arrayByteNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Byte::class.isInstance(it) } + } + } + } + + @Test + fun arrayDoubleNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Double::class.isInstance(it) } + } + } + } + + @Test + fun arrayFloatNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Float::class.isInstance(it) } + } + } + } + + @Test + fun arrayIntNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Int::class.isInstance(it) } + } + } + } + + @Test + fun arrayLongNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Long::class.isInstance(it) } + } + } + } + + @Test + fun arrayShortNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Short::class.isInstance(it) } + } + } + } + + @Test + fun arrayCharNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || Char::class.isInstance(it) } + } + } + } + + @Test + fun arrayUByteNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || UByte::class.isInstance(it) } + } + } + } + + @Test + fun arrayUIntNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || UInt::class.isInstance(it) } + } + } + } + + @Test + fun arrayULongNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || ULong::class.isInstance(it) } + } + } + } + + @Test + fun arrayUShortNullable() { + assertIsRandom { + fixture>().onEach { + assertTrue { it == null || UShort::class.isInstance(it) } + } + } + } +} diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/ComparisonTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/ComparisonTest.kt index e94d9d71..e0ea0576 100644 --- a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/ComparisonTest.kt +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/ComparisonTest.kt @@ -196,8 +196,30 @@ class ComparisonTest { @Suppress("DEPRECATION_ERROR") val result = appmattus.create(type, appmattus.fixtureConfiguration)!! - assertTrue { - resultClass.isInstance(result) + // Special handling of primitive arrays due to bug in the Kotlin type system + // See https://youtrack.jetbrains.com/issue/KT-52170/Reflection-typeOfArrayLong-gives-classifier-LongArray + if (type in listOf( + typeOf>(), + typeOf>(), + typeOf>(), + typeOf>(), + typeOf>(), + typeOf>(), + typeOf>(), + typeOf>() + ) + ) { + val argumentType = type.arguments[0].type?.classifier as KClass<*> + assertTrue { + result is Array<*> + } + (result as Array<*>).forEach { + argumentType.isInstance(it) + } + } else { + assertTrue { + resultClass.isInstance(result) + } } } @@ -433,8 +455,18 @@ class ComparisonTest { arrayOf(typeOf(), VALID, VALID, VALID, VALID), arrayOf(typeOf(), VALID, VALID, VALID, VALID), arrayOf(typeOf(), VALID, VALID, VALID, VALID), - arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), + arrayOf(typeOf>(), VALID, VALID, UNSUPPORTED, VALID), // Iterable, List arrayOf(typeOf>(), VALID, NOT_RANDOM, VALID, UNSUPPORTED), diff --git a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolverTest.kt b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolverTest.kt index eb1f6365..0da7eb75 100644 --- a/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolverTest.kt +++ b/fixture/src/test/kotlin/com/appmattus/kotlinfixture/resolver/ArrayKTypeResolverTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 Appmattus Limited + * Copyright 2020-2023 Appmattus Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,13 +46,6 @@ class ArrayKTypeResolverTest { assertTrue(result is Unresolved) } - @Test - fun `Array-Int class returns Unresolved`() { - val result = context.resolve(typeOf>()) - - assertTrue(result is Unresolved) - } - @Test fun `Array-String class returns string array`() { val result = context.resolve(typeOf>())