Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RandomClassProvider doesn't handle constructorless types correctly in all instances #204

Closed
samcgardner opened this issue Nov 27, 2023 · 1 comment
Labels
bug 🐞 Something isn't working core 🧬 Issue related to :core module
Milestone

Comments

@samcgardner
Copy link

RandomClassProvider has special handling for types like primitives and enums that don't have constructors. However, this special handling is only applied when mapping over direct constructor parameters. This means that constructor-less classes don't get built correctly if they are built under other circumstances, such as when they are included in a collection. For example:

package com.mr.reproducer

import io.github.serpro69.kfaker.Faker
import org.junit.jupiter.api.Test

data class Works(val case : AnEnum)

data class DoesNotWork(val case : List<AnEnum>)

enum class AnEnum {
    FOO,
    BAR
}

class TestCases {

    @Test
    fun `special handling occurs`() {
        Faker().randomProvider.randomClassInstance<Works>()
    }

    @Test
    fun `special handling does not occur`() {
        Faker().randomProvider.randomClassInstance<DoesNotWork>()
    }
}

Although I provide one specific way to reproduce this issue, anything that calls randomClassInstance can hit this bug. I believe this class of error could be handled for all cases by checking for constructorless cases at the top of randomClassInstance, but I haven't studied this question in detail.

If you're open to reviewing a pull request I'd be happy to submit one, but in either case thank you for your work on this project!

@serpro69
Copy link
Owner

Hi @samcgardner ,
Thanks for opening this issue!

Yes, you're correct, the above case is broken. This is because an enum doesn't have a constructor as such, so when an enum is a type of a collection - it will fail, particularly in this part

val constructor = constructors.firstOrNull {
it.parameters.size == config.constructorParamSize
} ?: when (config.constructorFilterStrategy) {
MIN_NUM_OF_ARGS -> constructors.minByOrNull { it.parameters.size }
MAX_NUM_OF_ARGS -> constructors.maxByOrNull { it.parameters.size }
else -> {
when (config.fallbackStrategy) {
FAIL_IF_NOT_FOUND -> {
throw NoSuchElementException("Constructor with 'parameters.size == ${config.constructorParamSize}' not found for $this")
}
USE_MIN_NUM_OF_ARGS -> constructors.minByOrNull { it.parameters.size }
USE_MAX_NUM_OF_ARGS -> constructors.maxByOrNull { it.parameters.size }
}
}
}
?: predefinedInstance?.let { return@run it }
?: throw NoSuchElementException("No suitable constructor or predefined instance found for $this")

The fix should be pretty simple I think. For example, if no constructors are found, we can check if the receiver is an enum or not, and then call randomEnumOrNull on the receiver.

I'm always open to PRs that fix issues :) If you want do that - please feel free to open a PR. Otherwise I can take a look at fixing this myself. Just let me know how you want to proceed.

@serpro69 serpro69 added bug 🐞 Something isn't working core 🧬 Issue related to :core module labels Nov 28, 2023
@serpro69 serpro69 added this to the 1.16.0 milestone Nov 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐞 Something isn't working core 🧬 Issue related to :core module
Projects
None yet
Development

No branches or pull requests

2 participants