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

Add support for JUnit 4 via junit-vintage #50

Merged
merged 3 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ import java.nio.file.Files
import kotlin.io.path.name
import org.junit.jupiter.api.Test

/** Search for any test annotation classes which are present on the classpath. */
private val testAnnotations =
listOf(
"org.junit.jupiter.api.Test", // junit5,
"org.junit.Test" // junit4
)
.mapNotNull {
try {
Class.forName(it).asSubclass(Annotation::class.java)
} catch (e: ClassNotFoundException) {
null
}
}

/**
* Searches the whole snapshot directory, finds all the `.ss` files, and prunes any which don't have
* matching test files anymore.
Expand All @@ -41,7 +55,9 @@ internal fun findStaleSnapshotFiles(layout: SnapshotFileLayout): List<String> {
}
private fun classExistsAndHasTests(key: String): Boolean {
return try {
Class.forName(key).declaredMethods.any { it.isAnnotationPresent(Test::class.java) }
Class.forName(key).methods.any { method ->
testAnnotations.any { method.isAnnotationPresent(it) }
}
} catch (e: ClassNotFoundException) {
false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.diffplug.selfie.RW
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.ConcurrentSkipListSet
import java.util.concurrent.atomic.AtomicReference
import org.junit.platform.engine.TestExecutionResult
import org.junit.platform.engine.support.descriptor.ClassSource
import org.junit.platform.engine.support.descriptor.MethodSource
Expand Down Expand Up @@ -121,6 +123,7 @@ internal class ClassProgress(val parent: Progress, val className: String) {
if (file!!.snapshots.isEmpty()) {
deleteFileAndParentDirIfEmpty(snapshotPath)
} else {
parent.markPathAsWritten(parent.layout.snapshotPathForClass(className))
Files.createDirectories(snapshotPath.parent)
Files.newBufferedWriter(snapshotPath, StandardCharsets.UTF_8).use { writer ->
file!!.serialize(writer::write)
Expand Down Expand Up @@ -227,10 +230,27 @@ internal class Progress {
}
}
}

private var checkForInvalidStale: AtomicReference<MutableSet<Path>?> =
AtomicReference(ConcurrentSkipListSet())
internal fun markPathAsWritten(path: Path) {
val written =
checkForInvalidStale.get()
?: throw AssertionError("Snapshot file is being written after all tests were finished.")
written.add(path)
}
fun finishedAllTests() {
val written =
checkForInvalidStale.getAndSet(null)
?: throw AssertionError("finishedAllTests() was called more than once.")
for (stale in findStaleSnapshotFiles(layout)) {
val path = layout.snapshotPathForClass(stale)
deleteFileAndParentDirIfEmpty(path)
if (written.contains(path)) {
throw AssertionError(
"Selfie wrote a snapshot and then marked it stale for deletion it in the same run: $path\nSelfie will delete this snapshot on the next run, which is bad! Why is Selfie marking this snapshot as stale?")
} else {
deleteFileAndParentDirIfEmpty(path)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.selfie.junitvintage

import com.diffplug.selfie.junit5.Harness
import kotlin.test.Test
import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.TestMethodOrder
import org.junitpioneer.jupiter.DisableIfTestFails

/** Simplest test for verifying read/write of a snapshot. */
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
@DisableIfTestFails
class ReadWriteVintageTest : Harness("undertest-junit-vintage") {
@Test @Order(1)
fun noSelfie() {
ut_snapshot().deleteIfExists()
ut_snapshot().assertDoesNotExist()
}

@Test @Order(2)
fun writeApple() {
ut_mirror().lineWith("apple").uncomment()
ut_mirror().lineWith("orange").commentOut()
gradleWriteSS()
ut_snapshot()
.assertContent(
"""
╔═ selfie ═╗
apple
╔═ [end of file] ═╗

"""
.trimIndent())
}

@Test @Order(3)
fun assertApplePasses() {
gradleReadSS()
}

@Test @Order(4)
fun assertOrangeFails() {
ut_mirror().lineWith("apple").commentOut()
ut_mirror().lineWith("orange").uncomment()
gradleReadSSFail()
ut_snapshot()
.assertContent(
"""
╔═ selfie ═╗
apple
╔═ [end of file] ═╗

"""
.trimIndent())
}

@Test @Order(5)
fun writeOrange() {
gradleWriteSS()
ut_snapshot()
.assertContent(
"""
╔═ selfie ═╗
orange
╔═ [end of file] ═╗

"""
.trimIndent())
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ blowdryerSetup {
include 'selfie-lib'
include 'selfie-runner-junit5'
include 'undertest-junit5'
include 'undertest-junit-vintage'
rootProject.name = 'selfie'
48 changes: 48 additions & 0 deletions undertest-junit-vintage/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
}
repositories {
mavenCentral()
}
apply plugin: 'com.diffplug.spotless'
spotless {
kotlin {
target 'src/**/*.kt'
toggleOffOn()
licenseHeader ''
ktfmt()
replaceRegex("test one-liner", "@Test\n(\\s*)fun ", "@Test fun ")
replaceRegex("test harness comments", "\n(\\s)*//", "\n//")
}
}

dependencies {
testImplementation project(':selfie-runner-junit5')
testCompileOnly "junit:junit:4.13.2"
testImplementation "org.junit.jupiter:junit-jupiter-api:${ver_JUNIT_USE}"
testImplementation "org.junit.vintage:junit-vintage-engine:${ver_JUNIT_USE}"
}
// this project is just a test environment for a different project
test {
enabled = false
}
tasks.register('underTest', Test) {
useJUnitPlatform()
testClassesDirs = testing.suites.test.sources.output.classesDirs
classpath = testing.suites.test.sources.runtimeClasspath
testLogging.showStandardStreams = true
// the snapshots are both output and input, for this harness best if the test just always runs
outputs.upToDateWhen { false }
// defaults to 'write'
systemProperty 'selfie', findProperty('selfie')
}
tasks.register('underTestRead', Test) {
useJUnitPlatform()
testClassesDirs = testing.suites.test.sources.output.classesDirs
classpath = testing.suites.test.sources.runtimeClasspath
testLogging.showStandardStreams = true
// the snapshots are both output and input, for this harness best if the test just always runs
outputs.upToDateWhen { false }
// read-only
systemProperty 'selfie', 'read'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package undertest.junit5

import com.diffplug.selfie.Selfie.expectSelfie
import org.junit.Test

class UT_ReadWriteVintageTest {
@Test fun selfie() {
// expectSelfie("apple").toMatchDisk()
expectSelfie("orange").toMatchDisk()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
╔═ selfie ═╗
orange
╔═ [end of file] ═╗
2 changes: 2 additions & 0 deletions undertest-junit5/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tasks.register('underTest', Test) {
useJUnitPlatform()
testClassesDirs = testing.suites.test.sources.output.classesDirs
classpath = testing.suites.test.sources.runtimeClasspath
testLogging.showStandardStreams = true
// the snapshots are both output and input, for this harness best if the test just always runs
outputs.upToDateWhen { false }
// defaults to 'write'
Expand All @@ -38,6 +39,7 @@ tasks.register('underTestRead', Test) {
useJUnitPlatform()
testClassesDirs = testing.suites.test.sources.output.classesDirs
classpath = testing.suites.test.sources.runtimeClasspath
testLogging.showStandardStreams = true
// the snapshots are both output and input, for this harness best if the test just always runs
outputs.upToDateWhen { false }
// read-only
Expand Down