diff --git a/buildSrc/src/main/kotlin/faker-lib-conventions.gradle.kts b/buildSrc/src/main/kotlin/faker-lib-conventions.gradle.kts index 6376bf3b5..e88590b42 100644 --- a/buildSrc/src/main/kotlin/faker-lib-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/faker-lib-conventions.gradle.kts @@ -84,6 +84,10 @@ dependencies { // we're shadowing these, so they need to be available for test runtime testRuntimeOnly(libs.icu4j) testRuntimeOnly(libs.rgxgen) + // needed to be able to run tests in intellij, no idea why... (gradle tests work fine from cli) + // clearly a bug with idea... + // maybe something related to https://youtrack.jetbrains.com/issue/IDEA-163411 + testRuntimeOnly(libs.bundles.jackson) } val integrationTest by tasks.creating(Test::class) { diff --git a/core/src/integration/kotlin/io/github/serpro69/kfaker/AbstractIT.kt b/core/src/integration/kotlin/io/github/serpro69/kfaker/AbstractIT.kt index c60c16185..c5eae98e3 100644 --- a/core/src/integration/kotlin/io/github/serpro69/kfaker/AbstractIT.kt +++ b/core/src/integration/kotlin/io/github/serpro69/kfaker/AbstractIT.kt @@ -6,6 +6,7 @@ import io.github.serpro69.kfaker.provider.misc.RandomProvider import io.github.serpro69.kfaker.provider.misc.StringProvider import io.kotest.core.spec.style.DescribeSpec import io.kotest.core.spec.style.scopes.DescribeSpecContainerScope +import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KProperty import kotlin.reflect.KVisibility @@ -69,6 +70,7 @@ abstract class AbstractIT : DescribeSpec { ) } +@Suppress("FunctionName") fun DescribeSpec.`describe all public generators`( test: suspend DescribeSpecContainerScope.(Faker, KProperty<*>, KFunction<*>) -> Unit ) { @@ -92,7 +94,7 @@ fun DescribeSpec.`describe all public generators`( describe("all public functions in each provider") { providerFunctions.forEach { (functions, provider) -> functions.forEach { - context("result value for ${provider.name} ${it.name} is resolved correctly") { + context("result value for ${provider.name}#${it.name} is resolved correctly") { test(this, faker, provider, it) } } @@ -100,4 +102,60 @@ fun DescribeSpec.`describe all public generators`( } } + +@Suppress("FunctionName") +fun DescribeSpec.`describe all public sub-providers`( + test: suspend DescribeSpecContainerScope.(FakeDataProvider, KProperty<*>, KFunction<*>) -> Unit +) { + val faker = Faker() + + // Get a list of all publicly visible providers + val providers: List> = faker::class.declaredMemberProperties.filter { + it.visibility == KVisibility.PUBLIC + && it.returnType.isSubtypeOf(FakeDataProvider::class.starProjectedType) + && it.returnType.classifier != Money::class // Ignore Money provider as it's a special case + && it.returnType.classifier != StringProvider::class // Ignore String provider + && it.returnType.classifier != RandomProvider::class // Ignore String provider + } + + // Get a list of all publicly visible sub-providers in each provider + val subProviders: List>>> = providers.mapNotNull { p -> + val provider = p.getter.call(faker)!! as FakeDataProvider + val subs = provider::class.declaredMemberProperties.filter { + it.visibility == KVisibility.PUBLIC + && it.returnType.isSubtypeOf(FakeDataProvider::class.starProjectedType) + && it.returnType.classifier as KClass<*> != provider::class // exclude 'unique' properties + } + if (subs.isNotEmpty()) provider to subs else null + } + + // Get a list of all publicly visible functions in each provider + val providerFunctions: GeneratorFunctions = providers.associateBy { provider -> + provider.getter.call(faker)!!::class.declaredMemberFunctions.filter { + it.visibility == KVisibility.PUBLIC && !it.annotations.any { ann -> ann is Deprecated } + } + } + + // Get all publicly visible functions in each sub-provider + val subProviderFunctions = subProviders.map { (provider, subs) -> + provider to subs.associateBy { sub -> + sub.getter.call(provider)!!::class.declaredMemberFunctions.filter { + it.visibility == KVisibility.PUBLIC && !it.annotations.any { ann -> ann is Deprecated } + } + } + } + + describe("all public functions in each sub-provider") { + subProviderFunctions.forEach { (provider, subs) -> + subs.forEach { (functions, sub) -> + functions.forEach { + context("result value for ${sub.name}#${it.name} is resolved correctly") { + test(this, provider, sub, it) + } + } + } + } + } +} + typealias GeneratorFunctions = Map>, KProperty<*>> diff --git a/core/src/integration/kotlin/io/github/serpro69/kfaker/FakerIT.kt b/core/src/integration/kotlin/io/github/serpro69/kfaker/FakerIT.kt index e0455ca7f..c7e826f24 100644 --- a/core/src/integration/kotlin/io/github/serpro69/kfaker/FakerIT.kt +++ b/core/src/integration/kotlin/io/github/serpro69/kfaker/FakerIT.kt @@ -1,5 +1,6 @@ package io.github.serpro69.kfaker +import io.github.serpro69.kfaker.provider.FakeDataProvider import io.kotest.assertions.assertSoftly import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe @@ -12,20 +13,7 @@ class FakerIT : AbstractIT({ t -> `describe all public generators` { faker, provider: KProperty<*>, f: KFunction<*> -> val regex = Regex("""#\{.*}|#++""") - val value = when (f.parameters.size) { - 1 -> f.call(provider.getter.call(faker)).toString() - 2 -> { - if (f.parameters[1].isOptional) { // optional params are enum typed (see functions in Dune, Finance or Tron, for example) - f.callBy(mapOf(f.parameters[0] to provider.getter.call(faker))).toString() - } else f.call(provider.getter.call(faker), "").toString() - } - 3 -> { - if (f.parameters[1].isOptional && f.parameters[2].isOptional) { - f.callBy(mapOf(f.parameters[0] to provider.getter.call(faker))).toString() - } else f.call(provider.getter.call(faker), "", "").toString() - } - else -> throw IllegalArgumentException("") - } + val value = value(f, provider, faker) it("resolved value should not contain yaml expression") { if ( @@ -64,6 +52,48 @@ class FakerIT : AbstractIT({ t -> } } + `describe all public sub-providers` { provider: FakeDataProvider, sub: KProperty<*>, f: KFunction<*> -> + val regex = Regex("""#\{.*}|#++""") + + val value = value(f, sub, provider) + + it("resolved value should not contain yaml expression") { + if ( + !value.contains("#chuck and #norris") + && (sub.name != "markdown" && f.name != "headers") + && value !in t.valuesWithHashKey + ) { + if (value.contains(regex)) { + throw AssertionError("Value '$value' for '${sub.name} ${f.name}' should not contain regex '$regex'") + } + } + } + + it("resolved value should not be empty string") { + if (value == "") { + throw AssertionError("Value for '${sub.name} ${f.name}' should not be empty string") + } + } + + it("resolved value should not contain duplicates") { + val values = value.split(" ") + + // Accounting for some exceptional cases where values are repeated + // in resolved expression + if ( + (sub.name != "coffee" && f.name != "notes") + && (sub.name != "onePiece" && f.name != "akumasNoMi") + && (sub.name != "lorem" && f.name != "punctuation" && value != " ") + && value !in t.duplicatedValues + ) { + // Since there's no way to modify assertion message in KotlinTest it's better to throw a custom error + if (values.odds() == values.evens()) { + throw AssertionError("Value '$value' for '${sub.name} ${f.name}' should not contain duplicates") + } + } + } + } + describe("Faker instance is initialized with default locale") { val faker = Faker() @@ -123,6 +153,26 @@ class FakerIT : AbstractIT({ t -> } }) +/** + * Return the value from the [f] member function of [p] property. + * + * @param args arguments to call [p] property getter + */ +private fun value(f: KFunction<*>, p: KProperty<*>, vararg args: Any?) = when (f.parameters.size) { + 1 -> f.call(p.getter.call(*args)).toString() + 2 -> { + if (f.parameters[1].isOptional) { // optional params are enum typed (see functions in Dune, Finance or Tron, for example) + f.callBy(mapOf(f.parameters[0] to p.getter.call(*args))).toString() + } else f.call(p.getter.call(*args), "").toString() + } + 3 -> { + if (f.parameters[1].isOptional && f.parameters[2].isOptional) { + f.callBy(mapOf(f.parameters[0] to p.getter.call(*args))).toString() + } else f.call(p.getter.call(*args), "", "").toString() + } + else -> throw IllegalArgumentException("") +} + private fun List.odds() = this.mapIndexedNotNull { index, s -> if (index % 2 == 0) s else null } diff --git a/test/src/test/kotlin/io/github/serpro69/kfaker/test/helper/IntegrationTestHelper.kt b/test/src/test/kotlin/io/github/serpro69/kfaker/test/helper/IntegrationTestHelper.kt index 835f033f3..bfeadeed2 100644 --- a/test/src/test/kotlin/io/github/serpro69/kfaker/test/helper/IntegrationTestHelper.kt +++ b/test/src/test/kotlin/io/github/serpro69/kfaker/test/helper/IntegrationTestHelper.kt @@ -7,7 +7,10 @@ import io.github.serpro69.kfaker.provider.misc.RandomProvider import io.github.serpro69.kfaker.provider.misc.StringProvider import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.DescribeSpec +import io.kotest.core.spec.style.scopes.DescribeSpecContainerScope import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.reflect.KClass +import kotlin.reflect.KFunction import kotlin.reflect.KProperty import kotlin.reflect.KVisibility import kotlin.reflect.full.declaredMemberFunctions @@ -26,6 +29,17 @@ fun DescribeSpec.`every public function in each provider is invoked without exce && it.returnType.classifier != RandomProvider::class // Ignore String provider } + // Get a list of all publicly visible sub-providers in each provider + val subProviders: List>>> = providers.mapNotNull { p -> + val provider = p.getter.call(faker)!! as FakeDataProvider + val subs = provider::class.declaredMemberProperties.filter { + it.visibility == KVisibility.PUBLIC + && it.returnType.isSubtypeOf(FakeDataProvider::class.starProjectedType) + && it.returnType.classifier as KClass<*> != provider::class // exclude 'unique' properties + } + if (subs.isNotEmpty()) provider to subs else null + } + // Get a list of all publicly visible functions in each provider val providerFunctions = providers.associateBy { provider -> provider.getter.call(faker)!!::class.declaredMemberFunctions.filter { @@ -33,63 +47,78 @@ fun DescribeSpec.`every public function in each provider is invoked without exce } } + // Get all publicly visible functions in each sub-provider + val subProviderFunctions = subProviders.map { (provider, subs) -> + provider to subs.associateBy { sub -> + sub.getter.call(provider)!!::class.declaredMemberFunctions.filter { + it.visibility == KVisibility.PUBLIC && !it.annotations.any { ann -> ann is Deprecated } + } + } + } + assertSoftly { providerFunctions.forEach { (functions, provider) -> - functions.forEach { - context("result value for ${provider.name} ${it.name} is resolved correctly") { - val regex = Regex("""#\{.*}|#++""") - - val value = when (it.parameters.size) { - 1 -> it.call(provider.getter.call(faker)).toString() - 2 -> { - if (it.parameters[1].isOptional) { // optional params are enum typed (see functions in Dune, Finance or Tron, for example) - it.callBy(mapOf(it.parameters[0] to provider.getter.call(faker))).toString() - } else it.call(provider.getter.call(faker), "").toString() - } - 3 -> { - if (it.parameters[1].isOptional && it.parameters[2].isOptional) { - it.callBy(mapOf(it.parameters[0] to provider.getter.call(faker))).toString() - } else it.call(provider.getter.call(faker), "", "").toString() - } - else -> throw IllegalArgumentException("") - } + test(provider, functions, faker) + } - it("resolved value should not contain yaml expression") { - if ( - !value.contains("#chuck and #norris") - && (provider.name != "markdown" && it.name != "headers") - && value !in valuesWithHashKey - ) { - if (value.contains(regex)) { - throw AssertionError("Value '$value' for '${provider.name} ${it.name}' should not contain regex '$regex'") - } - } - } + subProviderFunctions.forEach { (provider, subs) -> + subs.forEach { (functions, sub) -> test(sub, functions, provider) } + } + } + } +} - it("resolved value should not be empty string") { - if (value == "") { - throw AssertionError("Value for '${provider.name} ${it.name}' should not be empty string") - } - } +private suspend fun DescribeSpecContainerScope.test(provider: KProperty<*>, functions: List>, vararg caller: Any?) = functions.forEach { + context("result value for ${provider.name}#${it.name} is resolved correctly") { + val regex = Regex("""#\{.*}|#++""") - it("resolved value should not contain duplicates") { - val values = value.split(" ") - - // Accounting for some exceptional cases where values are repeated - // in resolved expression - if ( - (provider.name != "coffee" && it.name != "notes") - && (provider.name != "onePiece" && it.name != "akumasNoMi") - && (provider.name != "lorem" && it.name != "punctuation" && value != " ") - && value !in duplicatedValues - ) { - // Since there's no way to modify assertion message in KotlinTest it's better to throw a custom error - if (values.odds() == values.evens()) { - throw AssertionError("Value '$value' for '${provider.name} ${it.name}' should not contain duplicates") - } - } - } - } + val value = when (it.parameters.size) { + 1 -> it.call(provider.getter.call(*caller)).toString() + 2 -> { + if (it.parameters[1].isOptional) { // optional params are enum typed (see functions in Dune, Finance or Tron, for example) + it.callBy(mapOf(it.parameters[0] to provider.getter.call(*caller))).toString() + } else it.call(provider.getter.call(*caller), "").toString() + } + 3 -> { + if (it.parameters[1].isOptional && it.parameters[2].isOptional) { + it.callBy(mapOf(it.parameters[0] to provider.getter.call(*caller))).toString() + } else it.call(provider.getter.call(*caller), "", "").toString() + } + else -> throw IllegalArgumentException("") + } + + it("resolved value should not contain yaml expression") { + if ( + !value.contains("#chuck and #norris") + && (provider.name != "markdown" && it.name != "headers") + && value !in valuesWithHashKey + ) { + if (value.contains(regex)) { + throw AssertionError("Value '$value' for '${provider.name} ${it.name}' should not contain regex '$regex'") + } + } + } + + it("resolved value should not be empty string") { + if (value == "") { + throw AssertionError("Value for '${provider.name} ${it.name}' should not be empty string") + } + } + + it("resolved value should not contain duplicates") { + val values = value.split(" ") + + // Accounting for some exceptional cases where values are repeated + // in resolved expression + if ( + (provider.name != "coffee" && it.name != "notes") + && (provider.name != "onePiece" && it.name != "akumasNoMi") + && (provider.name != "lorem" && it.name != "punctuation" && value != " ") + && value !in duplicatedValues + ) { + // Since there's no way to modify assertion message in KotlinTest it's better to throw a custom error + if (values.odds() == values.evens()) { + throw AssertionError("Value '$value' for '${provider.name} ${it.name}' should not contain duplicates") } } }