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

Kotlin interoperability #52

Closed
tmohme opened this issue Mar 26, 2019 · 21 comments
Closed

Kotlin interoperability #52

tmohme opened this issue Mar 26, 2019 · 21 comments

Comments

@tmohme
Copy link
Contributor

@tmohme tmohme commented Mar 26, 2019

Testing Problem

In Kotlin we want to express something like this:

@Property
fun myTest(@ForAll bigDecimals : List<BigDecimal>) {...}

which is (almost) the equivalent to the following Java code:

@Property
void myTest(@ForAll List<BigDecimal> bigDecimals) {...}

What looks easy and works out of the box with Java produces a net.jqwik.api.CannotFindArbitraryException: Cannot find an Arbitrary for Parameter of type [@net.jqwik.api.ForAll(value=) List<? extends BigDecimal>] with Kotlin.
The difference is obviously the type signature which for Java is List<BigDecimal> while it is List<? extends BigDecimal> for Kotlin.
Due to this little difference, the BigDecimalArbitraryProvider decides that he is unable to provide values for the given targetType.

Suggested Solution

The BigDecimalArbitraryProvider could use the more forgivingtargetType.isAssignableFrom(BigDecimal.class) instead of the stricter targetType.isOfType(BigDecimal.class) to determine if he is able to provide the requested values.
The same applies to some of the other *ArbitraryProviders.
Interestingly, currently not all *ArbitraryProviders are structured the same.
While the providers for BigDecimal and BigInteger use isOfType(...) others (like Boolean, Integer, ...) use isAssignableFrom(...).

Discussion

We tried to get around this by creating our own provider for BigDecimals, but experienced problems in the aftermath when we had trouble to let our provider correctly work with jqwik constraint annotations (like @BigRange). Probably we are missing some knowledge here . . .

@jlink
Copy link
Owner

@jlink jlink commented Mar 26, 2019

There’s indeed some inconsistency about how built-in arbitrary providers match types. I‘ll have a look at it.

@jlink
Copy link
Owner

@jlink jlink commented Mar 26, 2019

Fixed in 22af655

Available in 1.1.2-SNAPSHOT

@jlink
Copy link
Owner

@jlink jlink commented Mar 26, 2019

@tmohme Your approach with registering your own provider should also work. @BigRange should be applicable as long as your own provider will create instances of type BigDecimalArbitrary or BigIntegerArbitrary respectively. If you want I can have a look at that code to see what the problem is.

@jlink jlink closed this as completed Mar 26, 2019
@jlink
Copy link
Owner

@jlink jlink commented Mar 26, 2019

I'm wondering in what situations Kotlin produces the generic subtype, though. Might lead to quite a few type matching problems in various situations...

@tmohme
Copy link
Contributor Author

@tmohme tmohme commented Mar 27, 2019

Fixed in 22af655

Thx for the super fast reaction :)

Your approach with registering your own provider should also work.

Thx for the hint and your offer. As I already wrote, we were probably missing some knowledge (and persistence) here.
Next time we stumble upon some problems, we'll delve a bit deeper into the subject.
No need for you to invest your time when we are just too lazy to do our homework ;)

I'm wondering in what situations Kotlin produces the generic subtype, though.

I'll play a bit with IntelliJ's Kolin byte-code viewer tomorrow . . . maybe I find out something useful.

@tmohme
Copy link
Contributor Author

@tmohme tmohme commented Apr 2, 2019

I'm wondering in what situations Kotlin produces the generic subtype, though. Might lead to quite a few type matching problems in various situations...

Just found out that you can use @JvmSuppressWildcards(suppress = true) on the test method to avoid this problem.
With this annotation in place the described problem does not exist.

@jlink
Copy link
Owner

@jlink jlink commented Apr 2, 2019

The change is more consistent anyway, And already deployed in 1.1.2. But good to know a workaround exists for future problems.

@asm0dey
Copy link

@asm0dey asm0dey commented Jul 13, 2019

Just got following on 1.1.6:

Jul 13, 2019 8:02:48 PM net.jqwik.engine.JqwikProperties loadProperties
INFO: No Jqwik properties file [jqwik.properties] found.

net.jqwik.api.CannotFindArbitraryException: Cannot find an Arbitrary for Parameter of type [@net.jqwik.api.ForAll(value=) @net.jqwik.api.constraints.Size(value=0, max=0, min=1) List<String>]

	at net.jqwik.engine.properties.RandomizedShrinkablesGenerator.resolveParameter(RandomizedShrinkablesGenerator.java:33)
	at net.jqwik.engine.properties.RandomizedShrinkablesGenerator.lambda$forParameters$0(RandomizedShrinkablesGenerator.java:21)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
	at net.jqwik.engine.properties.RandomizedShrinkablesGenerator.forParameters(RandomizedShrinkablesGenerator.java:22)
	at net.jqwik.engine.execution.CheckedProperty.createRandomizedShrinkablesGenerator(CheckedProperty.java:154)
	at net.jqwik.engine.execution.CheckedProperty.createDefaultShrinkablesGenerator(CheckedProperty.java:94)
	at net.jqwik.engine.execution.CheckedProperty.createShrinkablesGenerator(CheckedProperty.java:84)
	at net.jqwik.engine.execution.CheckedProperty.createGenericProperty(CheckedProperty.java:70)
	at net.jqwik.engine.execution.CheckedProperty.check(CheckedProperty.java:39)
	at net.jqwik.engine.execution.PropertyMethodExecutor.executeProperty(PropertyMethodExecutor.java:132)
	at net.jqwik.engine.execution.PropertyMethodExecutor.executeMethod(PropertyMethodExecutor.java:120)
	at net.jqwik.engine.execution.PropertyMethodExecutor.lambda$executePropertyMethod$2(PropertyMethodExecutor.java:100)
	at net.jqwik.api.lifecycle.AroundPropertyHook.lambda$static$0(AroundPropertyHook.java:17)
	at net.jqwik.api.lifecycle.AroundPropertyHook.lambda$null$1(AroundPropertyHook.java:32)
	at net.jqwik.engine.execution.lifecycle.AutoCloseableHook.aroundProperty(AutoCloseableHook.java:14)
	at net.jqwik.api.lifecycle.AroundPropertyHook.lambda$around$2(AroundPropertyHook.java:37)
	at net.jqwik.engine.execution.PropertyMethodExecutor.executePropertyMethod(PropertyMethodExecutor.java:98)
	at net.jqwik.engine.execution.PropertyMethodExecutor.execute(PropertyMethodExecutor.java:44)
	at net.jqwik.engine.execution.PropertyTaskCreator.executeTestMethod(PropertyTaskCreator.java:79)
	at net.jqwik.engine.execution.PropertyTaskCreator.lambda$createTask$0(PropertyTaskCreator.java:38)
	at net.jqwik.engine.execution.pipeline.ExecutionTask$1.execute(ExecutionTask.java:24)
	at net.jqwik.engine.execution.pipeline.ExecutionPipeline.runToTermination(ExecutionPipeline.java:81)
	at net.jqwik.engine.execution.JqwikExecutor.execute(JqwikExecutor.java:46)
	at net.jqwik.engine.JqwikTestEngine.executeTests(JqwikTestEngine.java:68)
	at net.jqwik.engine.JqwikTestEngine.execute(JqwikTestEngine.java:57)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

when signature is as following:

	@Property

	fun test(@ForAll @Size(min = 1) names: List<@StringLength(min = 1, max = 20) String>) {
		Assertions.assertEquals(names.toSet().joinToString(separator = ", "), CatalogUtils.toShortForm(names))

	}

@jlink jlink reopened this Jul 13, 2019
@jlink
Copy link
Owner

@jlink jlink commented Jul 13, 2019

@asm0dey Looking at the stack trace it should work, so
Since I'm not a Kotlin user: Can you point me to the simplest startup project (Gradle preferred) with which I can test it? And did you try @JvmSuppressWildcards(suppress = true) mentioned earlier in this thread?

@asm0dey
Copy link

@asm0dey asm0dey commented Jul 13, 2019

I have maven only and I'll try to narrow it down to minimum reproducible example .
And yes, I've tried to add annotation, no luck

@tmohme
Copy link
Contributor Author

@tmohme tmohme commented Jul 14, 2019

For me, the following slightly modified snippet runs flawlessly with Kolin 1.3.41, jqwik 1.1.6, Gradle 5.5.1:

    @Property
    fun test(@ForAll @Size(min = 1) names: List<@StringLength(min = 1, max = 20) String>) {
//        Assertions.assertEquals(names.toSet().joinToString(separator = ", "), CatalogUtils.toShortForm(names))
    }

I guess a complete example-project is required to investigate this.

@jlink
Copy link
Owner

@jlink jlink commented Jul 14, 2019

Just a wild guess: Could it be that Kotlin's nullable type feature leads to a type mismatch with Java's standard types?

@asm0dey
Copy link

@asm0dey asm0dey commented Jul 14, 2019

It could! I had to say that my code under test is written in Java. Does it change anything?

@jlink
Copy link
Owner

@jlink jlink commented Jul 14, 2019

If I only knew. Actually I'll need a repo in order to debug the live running code and see what's going on.

@asm0dey
Copy link

@asm0dey asm0dey commented Jul 14, 2019

And I'm absolutely unable to create simple example :(

@jlink
Copy link
Owner

@jlink jlink commented Jul 14, 2019

Start with giving the full code including class declaration and all imports. Maybe we can work from there.

@asm0dey
Copy link

@asm0dey asm0dey commented Jul 14, 2019

@jlink
Copy link
Owner

@jlink jlink commented Jul 15, 2019

@asm0dey I cannot find the tests in the repository which are probably the crucial part for tracking the problem.

My current inkling is that your project setup is not correct. If, for example, module jqwik-engine is not correctly inserted into the classpath the default arbitrary providers won't be loaded through Java's service loading mechanism which would materialize in the error you observed.

Maybe you can start from https://github.com/junit-team/junit5-samples/tree/r5.5.0/junit5-jupiter-starter-gradle-kotlin and then add jqwik as another testImplementation-dependency.

@asm0dey
Copy link

@asm0dey asm0dey commented Jul 15, 2019

Yeah, I didn't commit tests because they didn't work :) Anyways, I'll recheck everything again and will back to you if won't able to fix. Thank you!

@jlink
Copy link
Owner

@jlink jlink commented Aug 5, 2019

@asm0dey Any new insights?

@asm0dey
Copy link

@asm0dey asm0dey commented Aug 5, 2019

Nop. I think we should close this task until I have time to investigate

@jlink jlink closed this as completed Aug 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants