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

Improves Failures implementation #465

Merged
merged 2 commits into from
Nov 13, 2018

Conversation

LeoColman
Copy link
Member

As it was, the Failures class would fail to filter classes that are neither from sun.reflect.* nor from java.*. This commit aims to create a generic implementation that correctly filters stacktraces.

The implemented behavior observes a pattern to clean the stacktrace:

  • There may be elements that are not from Kotlintest and are not from User Classes, such as Reflection elements or Java elements (but may be others). These elements are removed.
  • Then the code tries to find elements that are indeed from Kotlintest, such as runners and other internals
  • After this, it's expected for user classes to be in the stacktrace, so the algorithm stops and returns the stacktrace

By observation of the FailuresTest class, one may believe that only classes from io.kotlintest and sun.reflect would be in the way of user classes, but that's not the case, and this commit fixes it.

Very related to this commit

As it was, the Failures class would fail to filter classes that are neither from `sun.reflect.*` nor from `java.*`. This commit aims to create a generic implementation that correctly filters stacktraces.

The implemented behavior observes a pattern to clean the stacktrace:

- There may be elements that are not from Kotlintest and are not from User Classes, such as Reflection elements or Java elements (but may be others). These elements are removed.
- Then the code tries to find elements that are indeed from Kotlintest, such as runners and other internals
- After this, it's expected for user classes to be in the stacktrace, so the algorithm stops and returns the stacktrace

By observation of the FailuresTest class, one may believe that only classes from `io.kotlintest` and `sun.reflect` would be in the way of user classes, but that's not the case, and this commit fixes it.
@LeoColman
Copy link
Member Author

@ajalt
Requested your review because you originally implemented the Failures algorithm. Would you mind taking a look?

@sksamuel
Copy link
Member

sksamuel commented Nov 7, 2018

Some context here - the reason for my change overnight was because when you have threads > 1, we execute these on a thread pool, and if threads == 1 we execute in the main thread. So the stack trace can vary. It won't always be the case that the "last" io.kotlintest stack element is directly before the user's own code.

@LeoColman
Copy link
Member Author

That's correct.
However, as I see, it won't always be sun.reflect or java.*, so I changed it to a way to find the user classes every time

Copy link
Contributor

@ajalt ajalt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have examples of stacktraces that were being filtered incorrectly before? We should add tests for them.

@LeoColman
Copy link
Member Author

@ajalt
The FailuresTest itself started failing when we made the change in the aforementioned commit. It was failing with the stacktrace:

java.lang.AssertionError: msg
	at io.kotlintest.runner.jvm.TestSetExecutor.execute(TestSetExecutor.kt:40)
	at io.kotlintest.runner.jvm.TestCaseExecutor.executeTestIfActive(TestCaseExecutor.kt:69)
	at io.kotlintest.runner.jvm.TestCaseExecutor.access$executeTestIfActive(TestCaseExecutor.kt:14)
	at io.kotlintest.runner.jvm.TestCaseExecutor$execute$3.invoke(TestCaseExecutor.kt:43)
	at io.kotlintest.runner.jvm.TestCaseExecutor.execute(TestCaseExecutor.kt:53)
	at io.kotlintest.runner.jvm.spec.SharedInstanceSpecRunner$execute$1.invoke(SharedInstanceSpecRunner.kt:16)
	at io.kotlintest.runner.jvm.spec.SharedInstanceSpecRunner$execute$1.invoke(SharedInstanceSpecRunner.kt:10)
	at io.kotlintest.runner.jvm.spec.SpecRunner$interceptSpec$chain$1.invoke(SpecRunner.kt:37)
	at io.kotlintest.runner.jvm.spec.SpecRunner$interceptSpec$chain$1.invoke(SpecRunner.kt:12)
	at io.kotlintest.runner.jvm.spec.SpecRunner.interceptSpec(SpecRunner.kt:38)
	at io.kotlintest.runner.jvm.spec.SharedInstanceSpecRunner.execute(SharedInstanceSpecRunner.kt:13)
	at io.kotlintest.runner.jvm.TestEngine.executeSpec(TestEngine.kt:118)
	at io.kotlintest.runner.jvm.TestEngine.access$executeSpec(TestEngine.kt:20)
	at io.kotlintest.runner.jvm.TestEngine$submitSpec$1.run(TestEngine.kt:97)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.RuntimeException
	at com.sksamuel.kotlintest.FailuresTest$1$1$1.invoke(FailuresTest.kt:15)
	at com.sksamuel.kotlintest.FailuresTest$1$1$1.invoke(FailuresTest.kt:10)
	at io.kotlintest.runner.jvm.TestSetExecutor.execute(TestSetExecutor.kt:40)
	at io.kotlintest.runner.jvm.TestCaseExecutor.executeTestIfActive(TestCaseExecutor.kt:69)
	at io.kotlintest.runner.jvm.TestCaseExecutor.access$executeTestIfActive(TestCaseExecutor.kt:14)
	at io.kotlintest.runner.jvm.TestCaseExecutor$execute$3.invoke(TestCaseExecutor.kt:43)
	at io.kotlintest.runner.jvm.TestCaseExecutor.execute(TestCaseExecutor.kt:53)
	at io.kotlintest.runner.jvm.spec.SharedInstanceSpecRunner$callingThreadContext$1.registerTestCase(SharedInstanceSpecRunner.kt:31)
	at io.kotlintest.TestContext.registerTestCase(TestContext.kt:41)
	at io.kotlintest.specs.AbstractFreeSpec$FreeSpecScope.invoke(AbstractFreeSpec.kt:47)
	at com.sksamuel.kotlintest.FailuresTest$1$1.invoke(FailuresTest.kt:14)
	at com.sksamuel.kotlintest.FailuresTest$1$1.invoke(FailuresTest.kt:10)
	at io.kotlintest.specs.AbstractFreeSpec$minus$1.invoke(AbstractFreeSpec.kt:18)
	at io.kotlintest.specs.AbstractFreeSpec$minus$1.invoke(AbstractFreeSpec.kt:11)
	... 19 more

With the fix the stacktrace is correct

… of val

Users may want to change this property dynamically, at runtime, for whichever reason. Changing it to a `var` won't impact anything else, so it's good to give users this freedom.
@sksamuel
Copy link
Member

@Kerooker @ajalt Why would it be a var ?

It's detected from the system properties so is still settable.
Or are you expecting people to do Failures.shouldRemoveKotlintestElementsFromStacktrace = false in code?

If so, this is better off being part of ProjectConfig which contains all the other "settings" that uses can override ?

@LeoColman
Copy link
Member Author

From what @ajalt said, I expected users to use Failures.shouldRemoveKotlintestElementsFromStacktrace = false. However, I cannot picture anyone actually doing this. I can revert the commit if necessary

@LeoColman LeoColman requested a review from ajalt November 12, 2018 13:08
@sksamuel
Copy link
Member

Having the programmatic version is useful, but I think it should be controlled from ProjectConfig that's all. That's where all the other settings are held.

@ajalt
Copy link
Contributor

ajalt commented Nov 12, 2018

We had the same discussion when I implemented this. Quoting from that PR:

Failures is part of kotlintest-assertions, and ProjectConfig is part of kotlintest-core, and neither depends on the other.

@LeoColman
Copy link
Member Author

Makes sense to me to keep them separated. But my concern is another one: Why is this customizable?
Do we have any user that alters this behavior at runtime?

Do we have any use for displaying Kotlintest internal stacktraces ever? I mean as an user. As a KT developer it may be interesting, but as an user I don't see use for this. What do you guys think?

@ajalt
Copy link
Contributor

ajalt commented Nov 12, 2018

Kotlintest is generally a very customizable framework. There isn't any cost to allowing this functionality to be configured, so why not? I'm sure that someone will want to turn off the magic at some point to help them debug.

@sksamuel
Copy link
Member

We had the same discussion when I implemented this. Quoting from that PR:

Failures is part of kotlintest-assertions, and ProjectConfig is part of kotlintest-core, and neither depends on the other.

Yes of course. Sorry I've blinked since we had that convo 😨

@sksamuel
Copy link
Member

Kotlintest is generally a very customizable framework. There isn't any cost to allowing this functionality to be configured, so why not? I'm sure that someone will want to turn off the magic at some point to help them debug.

Agreed. Val back to a var and I think we're good.

@LeoColman
Copy link
Member Author

Alright. Can we merge this then?

@sksamuel
Copy link
Member

I'm happy.

@LeoColman LeoColman merged commit 7b17e62 into master Nov 13, 2018
@LeoColman LeoColman deleted the kerooker/improve-failures-stacktrace-cleanse branch November 13, 2018 15:23
LeoColman added a commit that referenced this pull request Dec 4, 2018
* Improves Failures implementation

As it was, the Failures class would fail to filter classes that are neither from `sun.reflect.*` nor from `java.*`. This commit aims to create a generic implementation that correctly filters stacktraces.

The implemented behavior observes a pattern to clean the stacktrace:

- There may be elements that are not from Kotlintest and are not from User Classes, such as Reflection elements or Java elements (but may be others). These elements are removed.
- Then the code tries to find elements that are indeed from Kotlintest, such as runners and other internals
- After this, it's expected for user classes to be in the stacktrace, so the algorithm stops and returns the stacktrace

By observation of the FailuresTest class, one may believe that only classes from `io.kotlintest` and `sun.reflect` would be in the way of user classes, but that's not the case, and this commit fixes it.

* Changes `shouldRemoveKotlintestElementsFromStacktrace` to var instead of val

Users may want to change this property dynamically, at runtime, for whichever reason. Changing it to a `var` won't impact anything else, so it's good to give users this freedom.

(cherry picked from commit 7b17e62)
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

Successfully merging this pull request may close these issues.

3 participants