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

should not garbage collect snapshots when all tests are package-private #123

Closed
iaincd opened this issue Jan 24, 2024 · 10 comments · Fixed by #124
Closed

should not garbage collect snapshots when all tests are package-private #123

iaincd opened this issue Jan 24, 2024 · 10 comments · Fixed by #124
Labels
bug Something isn't working

Comments

@iaincd
Copy link

iaincd commented Jan 24, 2024

Hello! Excited to start using this project.

I have a test for some generated HTML that is currently using a hard-coded fixture (barely tolerable with text blocks), and converting it with Selfie.expectSelfie(output).toMatchDisk_TODO() was a breeze.

I am having an issue with snapshot persistence however, wherein a subsequent Gradle invocation removes the artifact, and thereafter the test fails unless I add //SELFIEWRITE (or revert to the _TODO() invocation).

I'll try to add more details later (environment, etc.), but for now, the useful error breadcrumb I see is:

java.lang.AssertionError: Selfie wrote a snapshot and then marked it stale for deletion it in the same run: <path snipped>
Selfie will delete this snapshot on the next run, which is bad! Why is Selfie marking this snapshot as stale?
    at com.diffplug.selfie.junit5.Progress.finishedAllTests(SelfieTestExecutionListener.kt:316)

Let me know if there's anything else besides environment info I can add later that might help track this down. I can take a swing at an MVCE if necessary too. Thanks!

@nedtwigg nedtwigg added the bug Something isn't working label Jan 24, 2024
@nedtwigg
Copy link
Member

It's likely that the test is being run in a way that our garbage collection doesn't recognize. Do any of the following describe your test? And if not, how is the test method annotated?

"org.junit.jupiter.api.Test", // junit5,
"org.junit.jupiter.params.ParameterizedTest",
"org.junit.Test" // junit4

@iaincd
Copy link
Author

iaincd commented Jan 25, 2024

The first one (JUnit Jupiter).

Scanning the javadocs there, it sounds like maybe my snapshot is not being named correctly — it gets named like EntireTestClass.ss, i.e. not having any specificity to the test method from which Selfie is invoked. Is that wholly unexpected?

I do have the Mockito JUnit extension in the mix also, I can see if removing that has any effect.

@nedtwigg
Copy link
Member

nedtwigg commented Jan 25, 2024

EntireTestClass.ss is expected, there is a 1:1 between test class and test snapshot file.

class NormalTestClass {
  @org.junit.jupiter.api.Test
  public void myTest() {
    expectSelfie("blah").toMatchDisk()

That's the most normal test class I can imagine. In what ways is yours different? Inner class? Custom test annotation?

@iaincd
Copy link
Author

iaincd commented Jan 25, 2024

My problem seems to arise here:

private fun classExistsAndHasTests(key: String): Boolean {
return try {
Class.forName(key).methods.any { method ->
testAnnotations.any { method.isAnnotationPresent(it) }
}
} catch (e: ClassNotFoundException) {
false
}
}

Class.forName(key) seems to yield only the ~8 base methods of Object.class, and thus has no chance of finding my regular old @org.junit.jupiter.api.Test-annotated methods.

key looks to correctly reflect the test class name. 🤔

Could it be the case that this only works for public test methods? It is idiomatic in JUnit 5 for test methods to be package-private.

@iaincd
Copy link
Author

iaincd commented Jan 25, 2024

I think that must be it — changing my test method signature from

@Test
void foo() {...}

...to:

@Test
public void foo() {...}

...no longer results in my snapshot being GC'd.

Do you think it would take much to handle package-private test methods? Common linters complain these days if JUnit 5 tests have unnecessary visibility.

@nedtwigg
Copy link
Member

Amazing, thanks! A fix is on the way...

@nedtwigg
Copy link
Member

Interesting... I added a reproducer in the PR above but it is working for public/package/protected.

What JRE are you on, and are you using a module-info.java file? I wonder if there's a module thing going on...

@iaincd
Copy link
Author

iaincd commented Jan 25, 2024

JDK: Coretto 17.0.7 (2023-04-18 LTS)
Kotlin: 1.9.0
Gradle: 7.4.2

No module-info.java in the project. The jvm-test-suite Gradle plugin is in use, which might be relevant somehow, although it doesn't seem to be a matter of failing to locate the test sources.

Happy to try changing any of those versions on my end if it will help shed any light!

@nedtwigg nedtwigg changed the title java.lang.AssertionError: Selfie wrote a snapshot and then marked it stale for deletion it in the same run should not garbage collect snapshots when all tests are package-private Jan 25, 2024
@nedtwigg
Copy link
Member

Fixed in 1.1.1, just published, might take a minute to get through the caching layers.

The reproducer wasn't working because as long as there was at least one public method then it worked. I got fixated on the modulepath thing, but that was a red herring.

Here's the commit where the reproducer fails, fixing it was straightforward from there: 8e59325

Thanks for reporting!

@iaincd
Copy link
Author

iaincd commented Jan 25, 2024

You’re welcome, thank you for shipping a fix so quickly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants