Add conditional module: JUnit conditional test execution on 6.1.3 (JDK 25)
Fixtures for the OS, JRE, system property, environment variable and EnabledIf conditions plus a custom ExecutionCondition, run through the JUnit Platform launcher (in-process and in child JVMs) to capture real skip reasons; skipped versus aborted lifecycle; stacking, class-level skips and the deactivate switch; the 2025 article's code compiled as published; and a javap comparison of the condition package in 5.14.4 and 6.1.3. Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01G8ikz8xdWuTP5yun8DZ1hk
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
target/
|
||||||
|
*.class
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# conditional
|
||||||
|
|
||||||
|
Companion code for [JUnit Conditional Test Execution (JUnit 5 → 6)](https://ankurm.com/mastering-junit-5-conditional-execution-build-smarter-environment-aware-tests/) on ankurm.com. The assumptions half of the story is in [JUnit 6 Assumptions](https://ankurm.com/junit-6-assumptions/).
|
||||||
|
|
||||||
|
The condition annotations (`@EnabledOnOs`, `@EnabledOnJre`, `@EnabledIfSystemProperty`, `@EnabledIfEnvironmentVariable`, `@EnabledIf`) and the `ExecutionCondition` extension point, run on JUnit Jupiter 6.1.3 and JDK 25. Every message and result quoted in the article is in [`output/`](output).
|
||||||
|
|
||||||
|
## How the evidence is produced
|
||||||
|
|
||||||
|
The *fixtures* in `src/test/java/.../fixtures/` are test classes that are meant to be skipped, aborted or to pass depending on the machine. They are excluded from Surefire. `ConditionalTest` starts the JUnit Platform on one fixture at a time (`Runner`), collects the result and the skip reason of each test, and writes the evidence file. For things a test cannot change in its own JVM (environment variables, `-D` flags) it starts a fresh JVM (`Child`) with the environment it wants.
|
||||||
|
|
||||||
|
| File | What it shows |
|
||||||
|
|---|---|
|
||||||
|
| `output/01-os-and-jre.txt` | The 2025 article's OS and JRE examples on this machine, with the real skip reasons |
|
||||||
|
| `output/02-architecture.txt` | `architectures` on `@EnabledOnOs` / `@DisabledOnOs` |
|
||||||
|
| `output/03-system-properties.txt` | `@EnabledIfSystemProperty` in three fresh JVMs |
|
||||||
|
| `output/04-environment-variables.txt` | `@EnabledIfEnvironmentVariable` with `CI` and `ENV` set and unset |
|
||||||
|
| `output/05-custom-condition.txt` | The article's `WeekendCondition`, and a reusable `@EnabledOnDays` condition with the day pinned |
|
||||||
|
| `output/06-skipped-versus-aborted.txt` | A condition-skipped test never starts; an assumption-aborted one has already run `@BeforeEach` |
|
||||||
|
| `output/07-stacking-and-switches.txt` | Several conditions on one test, `disabledReason`, `@EnabledIf("method")`, class-level skips, and switching conditions off |
|
||||||
|
| `output/08-old-code-vs-junit-6.txt` | The 2025 article's code blocks compiled exactly as published |
|
||||||
|
| `output/09-api-5-vs-6.txt` | `org.junit.jupiter.api.condition` in junit-jupiter-api 5.14.4 and 6.1.3 compared with `javap` |
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/run-all.sh # tests -> 01..07, then the compile and API comparison -> 08, 09
|
||||||
|
```
|
||||||
|
|
||||||
|
Needs JDK 17 or newer (built on 25) and Maven. Script 08/09 downloads two jars from Maven Central (it retries on rate limiting).
|
||||||
|
|
||||||
|
## Honest limits
|
||||||
|
|
||||||
|
Outputs 01, 02 and 07 describe the machine they ran on (Linux, amd64, JDK 25.0.4.1), so the OS, architecture and JRE conditions were only observed on that one combination. The first block of 05 depends on the day the tests ran. `src/old/` holds the 2025 article's code blocks verbatim; the copy under `fixtures/` adds a `package` line, and for `WeekendCondition` the `Test` import that the published block was missing.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
This machine: os.name=Linux, os.arch=amd64, java.version=25.0.4.1
|
||||||
|
OS.current()=LINUX, JRE.currentVersion()=JAVA_25
|
||||||
|
|
||||||
|
OsConditionalTest
|
||||||
|
runEverywhereButSolaris() : PASSED
|
||||||
|
testUnixSocketBehavior() : PASSED
|
||||||
|
testWindowsRegistryAccess() : SKIPPED (Disabled on operating system: Linux)
|
||||||
|
|
||||||
|
JreConditionalTest
|
||||||
|
hideFromExperimentalVersions() : PASSED
|
||||||
|
testLegacyCompatibility() : SKIPPED (Disabled on JRE version: 25.0.4.1)
|
||||||
|
testLtsFeatureSet() : SKIPPED (Disabled on JRE version: 25.0.4.1)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
This machine: os.name=Linux, os.arch=amd64, java.version=25.0.4.1
|
||||||
|
OS.current()=LINUX, JRE.currentVersion()=JAVA_25
|
||||||
|
|
||||||
|
ArchitectureConditionalTest
|
||||||
|
anythingButArm() : PASSED
|
||||||
|
onlyArmLinux() : SKIPPED (Disabled on operating system: Linux (amd64))
|
||||||
|
onlyIntelLinux() : PASSED
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
SystemPropertyTest in a fresh JVM, three times.
|
||||||
|
|
||||||
|
No -D flags:
|
||||||
|
runOnlyHighSeverityTests() : SKIPPED (System property [test.severity] does not exist)
|
||||||
|
testEnglishLocalization() : PASSED
|
||||||
|
|
||||||
|
-Dtest.severity=high -Duser.language=fr (the command line from the 2025 article):
|
||||||
|
runOnlyHighSeverityTests() : PASSED
|
||||||
|
testEnglishLocalization() : SKIPPED (System property [user.language] with value [fr] does not match regular expression [en])
|
||||||
|
|
||||||
|
-Dtest.severity=low -Duser.language=en:
|
||||||
|
runOnlyHighSeverityTests() : SKIPPED (System property [test.severity] with value [low] does not match regular expression [critical|high])
|
||||||
|
testEnglishLocalization() : PASSED
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
PipelineTest in a fresh JVM, three times, with the environment set by the test.
|
||||||
|
|
||||||
|
CI unset, ENV=DEV (a developer laptop):
|
||||||
|
integrationWithExternalAPI() : SKIPPED (Environment variable [ENV] with value [DEV] does not match regular expression [STAGING])
|
||||||
|
performanceBenchmark() : SKIPPED (Environment variable [CI] does not exist)
|
||||||
|
|
||||||
|
CI=true, ENV=STAGING (a pipeline):
|
||||||
|
integrationWithExternalAPI() : PASSED
|
||||||
|
performanceBenchmark() : PASSED
|
||||||
|
|
||||||
|
CI=false, ENV unset:
|
||||||
|
integrationWithExternalAPI() : SKIPPED (Environment variable [ENV] does not exist)
|
||||||
|
performanceBenchmark() : SKIPPED (Environment variable [CI] with value [false] does not match regular expression [true])
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
The WeekendCondition from the 2025 article, run today (2026-10-11, a SUNDAY):
|
||||||
|
weeklyBackupStabilityTest() : PASSED
|
||||||
|
|
||||||
|
The reusable version, @EnabledOnDays + DayOfWeekCondition, with the day pinned by the configuration parameter demo.today.
|
||||||
|
|
||||||
|
demo.today=SATURDAY
|
||||||
|
noAnnotationRunsAlways() : PASSED
|
||||||
|
wednesdayOnly() : SKIPPED (Today is SATURDAY, test runs on [WEDNESDAY])
|
||||||
|
weekendOnly() : PASSED
|
||||||
|
|
||||||
|
demo.today=WEDNESDAY
|
||||||
|
noAnnotationRunsAlways() : PASSED
|
||||||
|
wednesdayOnly() : PASSED
|
||||||
|
weekendOnly() : SKIPPED (Today is WEDNESDAY, test runs on [SATURDAY, SUNDAY])
|
||||||
|
|
||||||
|
demo.today=FRIDAY
|
||||||
|
noAnnotationRunsAlways() : PASSED
|
||||||
|
wednesdayOnly() : SKIPPED (Today is FRIDAY, test runs on [WEDNESDAY])
|
||||||
|
weekendOnly() : SKIPPED (Today is FRIDAY, test runs on [SATURDAY, SUNDAY])
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Results
|
||||||
|
a_runs() : PASSED
|
||||||
|
b_skippedByAnnotation() : SKIPPED (Disabled on operating system: Linux)
|
||||||
|
c_abortedByAssumption() : ABORTED (Assumption failed: database is down)
|
||||||
|
|
||||||
|
Every callback and test body that ran, in order
|
||||||
|
@BeforeEach a_runs
|
||||||
|
body a_runs
|
||||||
|
@AfterEach a_runs
|
||||||
|
@BeforeEach c_abortedByAssumption
|
||||||
|
@AfterEach c_abortedByAssumption
|
||||||
|
|
||||||
|
The AssumptionTest from the 2025 article, unchanged
|
||||||
|
testDatabaseConnection() : ABORTED (Assumption failed: Aborting: Database is not reachable.)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
This machine: os.name=Linux, os.arch=amd64, java.version=25.0.4.1
|
||||||
|
OS.current()=LINUX, JRE.currentVersion()=JAVA_25
|
||||||
|
|
||||||
|
StackingConditionalTest
|
||||||
|
bothConditionsFalse() : SKIPPED (Disabled on operating system: Linux)
|
||||||
|
methodConditionDefaultReason() : SKIPPED (@EnabledIf("alwaysFalse") evaluated to false)
|
||||||
|
methodConditionOwnReason() : SKIPPED (feature flag is off)
|
||||||
|
onlySecondConditionFalse() : SKIPPED (System property [never.set] does not exist)
|
||||||
|
withOwnReason() : SKIPPED (Disabled on operating system: Linux ==> Needs the Windows registry)
|
||||||
|
|
||||||
|
ClassLevelConditionalTest (@EnabledOnOs(WINDOWS) on the class, two test methods)
|
||||||
|
ClassLevelConditionalTest : CONTAINER SKIPPED (Disabled on operating system: Linux)
|
||||||
|
|
||||||
|
OsConditionalTest again, with the configuration parameter
|
||||||
|
junit.jupiter.conditions.deactivate=org.junit.jupiter.api.condition.*
|
||||||
|
runEverywhereButSolaris() : PASSED
|
||||||
|
testUnixSocketBehavior() : PASSED
|
||||||
|
testWindowsRegistryAccess() : PASSED
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# The 2025 article's code blocks, compiled exactly as published, against JUnit Jupiter 6.1.3 on JDK 25.0.4.1
|
||||||
|
|
||||||
|
AssumptionTest: compiles
|
||||||
|
JreConditionalTest: compiles
|
||||||
|
OsConditionalTest: compiles
|
||||||
|
PipelineTest: compiles
|
||||||
|
SystemPropertyTest: compiles
|
||||||
|
WeekendCondition: DOES NOT COMPILE
|
||||||
|
src/old/WeekendCondition.java:19: error: cannot find symbol
|
||||||
|
symbol: class Test
|
||||||
|
location: class CustomConditionTest
|
||||||
|
1 error
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# org/junit/jupiter/api/condition in junit-jupiter-api 5.14.4 (last JUnit 5 release on Maven Central) and 6.1.3
|
||||||
|
|
||||||
|
class file major version of EnabledOnOs in 5.14.4: 52 (52 = Java 8, 61 = Java 17)
|
||||||
|
class file major version of EnabledOnOs in 6.1.3: 61 (52 = Java 8, 61 = Java 17)
|
||||||
|
|
||||||
|
classes in the package: 39 in 5.14.4, 40 in 6.1.3
|
||||||
|
classes only in 6.1.3: org/junit/jupiter/api/condition/package-info.class
|
||||||
|
classes only in 5.14.4:
|
||||||
|
|
||||||
|
classes with an identical public and protected signature (javap): 37
|
||||||
|
classes whose signature differs: org.junit.jupiter.api.condition.AbstractJreRangeCondition org.junit.jupiter.api.condition.JRE
|
||||||
|
|
||||||
|
diff of javap org.junit.jupiter.api.condition.AbstractJreRangeCondition (5.14.4 -> 6.1.3):
|
||||||
|
4a5
|
||||||
|
> static {};
|
||||||
|
|
||||||
|
diff of javap org.junit.jupiter.api.condition.JRE (5.14.4 -> 6.1.3):
|
||||||
|
22a23,24
|
||||||
|
> public static final org.junit.jupiter.api.condition.JRE JAVA_27;
|
||||||
|
> public static final org.junit.jupiter.api.condition.JRE JAVA_28;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.ankurm</groupId>
|
||||||
|
<artifactId>conditional</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>conditional</name>
|
||||||
|
<description>JUnit conditional test execution (@EnabledOn*, ExecutionCondition) from JUnit 5 to JUnit 6.1.3 on JDK 25.</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>25</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<junit.version>6.1.3</junit.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>3.27.7</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- the tests start the JUnit Platform themselves (Runner), so the launcher API is on the compile classpath -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.platform</groupId>
|
||||||
|
<artifactId>junit-platform-launcher</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
<configuration>
|
||||||
|
<!-- fixtures are test classes that are meant to be skipped or to fail: only Runner runs them -->
|
||||||
|
<excludes>
|
||||||
|
<exclude>**/fixtures/**</exclude>
|
||||||
|
</excludes>
|
||||||
|
<!-- the tests start child JVMs with this module's classpath, which needs a real java.class.path -->
|
||||||
|
<useManifestOnlyJar>false</useManifestOnlyJar>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
Executable
+70
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Compiles the 2025 article's code blocks exactly as published (src/old/), and compares the
|
||||||
|
# org.junit.jupiter.api.condition package between the last JUnit 5 release and 6.1.3.
|
||||||
|
# Writes output/08-old-code-vs-junit-6.txt and output/09-api-5-vs-6.txt.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
mkdir -p output target/old target/cmp
|
||||||
|
mvn -q -B dependency:build-classpath -Dmdep.outputFile=target/cp.txt >/dev/null 2>&1
|
||||||
|
CP=$(cat target/cp.txt)
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# The 2025 article's code blocks, compiled exactly as published, against JUnit Jupiter 6.1.3 on JDK $(java -version 2>&1 | grep -o '"[^"]*"' | head -1 | tr -d '"')"
|
||||||
|
echo
|
||||||
|
for f in src/old/*.java; do
|
||||||
|
name=$(basename "$f" .java)
|
||||||
|
rm -rf target/old/out && mkdir -p target/old/out
|
||||||
|
if err=$(javac --release 25 -proc:none -cp "$CP" -d target/old/out "$f" 2>&1); then
|
||||||
|
echo "$name: compiles"
|
||||||
|
else
|
||||||
|
echo "$name: DOES NOT COMPILE"
|
||||||
|
echo "$err" | grep -E "error|symbol|location" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
} > output/08-old-code-vs-junit-6.txt
|
||||||
|
cat output/08-old-code-vs-junit-6.txt
|
||||||
|
|
||||||
|
V5=5.14.4
|
||||||
|
V6=6.1.3
|
||||||
|
fetch() { # version -> target/cmp/api-<version>.jar, retrying while Maven Central answers 429
|
||||||
|
local v=$1 code
|
||||||
|
if unzip -tq target/cmp/api-$v.jar >/dev/null 2>&1; then return; fi
|
||||||
|
for try in 1 2 3 4 5 6; do
|
||||||
|
code=$(curl -s -o target/cmp/api-$v.jar -w '%{http_code}' \
|
||||||
|
"https://repo1.maven.org/maven2/org/junit/jupiter/junit-jupiter-api/$v/junit-jupiter-api-$v.jar" || echo 000)
|
||||||
|
[ "$code" = "200" ] && return
|
||||||
|
sleep $((try * 8))
|
||||||
|
done
|
||||||
|
echo "could not download junit-jupiter-api $v (HTTP $code)" >&2; exit 1
|
||||||
|
}
|
||||||
|
fetch $V5
|
||||||
|
fetch $V6
|
||||||
|
pkg=org/junit/jupiter/api/condition
|
||||||
|
{
|
||||||
|
echo "# $pkg in junit-jupiter-api $V5 (last JUnit 5 release on Maven Central) and $V6"
|
||||||
|
echo
|
||||||
|
for v in $V5 $V6; do
|
||||||
|
major=$(javap -v -cp target/cmp/api-$v.jar org.junit.jupiter.api.condition.EnabledOnOs 2>/dev/null | grep "major version" | awk '{print $3}')
|
||||||
|
echo "class file major version of EnabledOnOs in $v: $major (52 = Java 8, 61 = Java 17)"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
for v in $V5 $V6; do unzip -l target/cmp/api-$v.jar | grep "$pkg/" | awk '{print $4}' | sort > target/cmp/cls-$v.txt; done
|
||||||
|
echo "classes in the package: $(wc -l < target/cmp/cls-$V5.txt) in $V5, $(wc -l < target/cmp/cls-$V6.txt) in $V6"
|
||||||
|
echo "classes only in $V6: $(comm -13 target/cmp/cls-$V5.txt target/cmp/cls-$V6.txt | tr '\n' ' ')"
|
||||||
|
echo "classes only in $V5: $(comm -23 target/cmp/cls-$V5.txt target/cmp/cls-$V6.txt | tr '\n' ' ')"
|
||||||
|
echo
|
||||||
|
same=0; changed=""
|
||||||
|
for c in $(comm -12 target/cmp/cls-$V5.txt target/cmp/cls-$V6.txt | sed 's/\.class$//;s#/#.#g'); do
|
||||||
|
a=$({ javap -cp target/cmp/api-$V5.jar "$c" 2>&1 || true; } | grep -v Picked | md5sum || true)
|
||||||
|
b=$({ javap -cp target/cmp/api-$V6.jar "$c" 2>&1 || true; } | grep -v Picked | md5sum || true)
|
||||||
|
if [ "$a" = "$b" ]; then same=$((same+1)); else changed="$changed $c"; fi
|
||||||
|
done
|
||||||
|
echo "classes with an identical public and protected signature (javap): $same"
|
||||||
|
echo "classes whose signature differs:${changed:- none}"
|
||||||
|
for c in $changed; do
|
||||||
|
echo
|
||||||
|
echo "diff of javap $c ($V5 -> $V6):"
|
||||||
|
diff <(javap -cp target/cmp/api-$V5.jar "$c" 2>&1 | { grep -v Picked || true; }) <(javap -cp target/cmp/api-$V6.jar "$c" 2>&1 | { grep -v Picked || true; }) | sed 's/^/ /' || true
|
||||||
|
done
|
||||||
|
} > output/09-api-5-vs-6.txt
|
||||||
|
cat output/09-api-5-vs-6.txt
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerates every file under output/: 01-07 from the tests, 08-09 from the old-snippet and API-diff script.
|
||||||
|
# No services needed. 01, 02 and 07 describe the machine they ran on; 05's first block depends on the day it runs.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
mvn -q -B test 2>&1 | grep -E "Tests run:|BUILD|FAIL|ERROR" || true
|
||||||
|
bash scripts/compile-old-snippets.sh >/dev/null
|
||||||
|
ls output
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assumptions.*;
|
||||||
|
|
||||||
|
class AssumptionTest {
|
||||||
|
@Test
|
||||||
|
void testDatabaseConnection() {
|
||||||
|
boolean isDbUp = false; // Mocking a down database
|
||||||
|
assumeTrue(isDbUp, "Aborting: Database is not reachable.");
|
||||||
|
|
||||||
|
System.out.println("This will not print because the assumption fails.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.*;
|
||||||
|
|
||||||
|
class JreConditionalTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnJre(JRE.JAVA_8)
|
||||||
|
void testLegacyCompatibility() {
|
||||||
|
System.out.println("Running on legacy Java 8 environment.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_21)
|
||||||
|
void testLtsFeatureSet() {
|
||||||
|
System.out.println("Running on a modern LTS version.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisabledOnJre(JRE.OTHER)
|
||||||
|
void hideFromExperimentalVersions() {
|
||||||
|
System.out.println("Running on a stable, recognized JRE.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.*;
|
||||||
|
|
||||||
|
class OsConditionalTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
void testWindowsRegistryAccess() {
|
||||||
|
System.out.println("Executing Windows-specific logic.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs({OS.LINUX, OS.MAC})
|
||||||
|
void testUnixSocketBehavior() {
|
||||||
|
System.out.println("Executing Unix-specific logic.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisabledOnOs(OS.SOLARIS)
|
||||||
|
void runEverywhereButSolaris() {
|
||||||
|
System.out.println("Solaris is not supported for this module.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||||
|
|
||||||
|
class PipelineTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfEnvironmentVariable(named = "CI", matches = "true")
|
||||||
|
void performanceBenchmark() {
|
||||||
|
System.out.println("Benchmark running on dedicated CI worker.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfEnvironmentVariable(named = "ENV", matches = "STAGING")
|
||||||
|
void integrationWithExternalAPI() {
|
||||||
|
System.out.println("Connecting to Staging API...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||||
|
|
||||||
|
class SystemPropertyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfSystemProperty(named = "test.severity", matches = "critical|high")
|
||||||
|
void runOnlyHighSeverityTests() {
|
||||||
|
System.out.println("Running a high-priority test case.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||||
|
void testEnglishLocalization() {
|
||||||
|
System.out.println("Testing English locale specific logic.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.*;
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class WeekendCondition implements ExecutionCondition {
|
||||||
|
@Override
|
||||||
|
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
|
||||||
|
DayOfWeek today = LocalDate.now().getDayOfWeek();
|
||||||
|
if (today == DayOfWeek.SATURDAY || today == DayOfWeek.SUNDAY) {
|
||||||
|
return ConditionEvaluationResult.enabled("It is the weekend!");
|
||||||
|
}
|
||||||
|
return ConditionEvaluationResult.disabled("Skipping: Today is " + today);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage in your test class
|
||||||
|
class CustomConditionTest {
|
||||||
|
@Test
|
||||||
|
@ExtendWith(WeekendCondition.class)
|
||||||
|
void weeklyBackupStabilityTest() {
|
||||||
|
System.out.println("Running heavy weekend-only backup test.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/** Runs Runner in a fresh JVM with a chosen environment and -D flags, and returns its result lines. */
|
||||||
|
final class Child {
|
||||||
|
|
||||||
|
private Child() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> run(String fixture, Map<String, String> env, Set<String> unsetEnv, Map<String, String> sysProps, String... configuration) {
|
||||||
|
try {
|
||||||
|
List<String> cmd = new ArrayList<>();
|
||||||
|
cmd.add(Path.of(System.getProperty("java.home"), "bin", "java").toString());
|
||||||
|
sysProps.forEach((k, v) -> cmd.add("-D" + k + "=" + v));
|
||||||
|
cmd.add("-cp");
|
||||||
|
cmd.add(System.getProperty("java.class.path"));
|
||||||
|
cmd.add(Runner.class.getName());
|
||||||
|
cmd.add(fixture);
|
||||||
|
cmd.addAll(List.of(configuration));
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(cmd).redirectErrorStream(true);
|
||||||
|
unsetEnv.forEach(pb.environment()::remove);
|
||||||
|
pb.environment().putAll(env);
|
||||||
|
Process p = pb.start();
|
||||||
|
String out = new String(p.getInputStream().readAllBytes());
|
||||||
|
if (p.waitFor() != 0) {
|
||||||
|
throw new IllegalStateException("child JVM failed:\n" + out);
|
||||||
|
}
|
||||||
|
return out.lines().filter(l -> l.startsWith("@@ ")).map(l -> l.substring(3)).toList();
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional;
|
||||||
|
|
||||||
|
import com.ankurm.tutorials.junit.conditional.Runner.Result;
|
||||||
|
import com.ankurm.tutorials.junit.conditional.fixtures.*;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.JRE;
|
||||||
|
import org.junit.jupiter.api.condition.OS;
|
||||||
|
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ConditionalTest {
|
||||||
|
|
||||||
|
private static final Map<String, String> NONE = Map.of();
|
||||||
|
|
||||||
|
private static String lines(List<?> items) {
|
||||||
|
return items.stream().map(i -> " " + i).collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String machine() {
|
||||||
|
return "This machine: os.name=" + System.getProperty("os.name") + ", os.arch=" + System.getProperty("os.arch")
|
||||||
|
+ ", java.version=" + System.getProperty("java.version")
|
||||||
|
+ "\n OS.current()=" + OS.current() + ", JRE.currentVersion()=" + JRE.currentVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result only(List<Result> results, String method) {
|
||||||
|
return results.stream().filter(r -> r.name().equals(method + "()")).findFirst().orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ 1. operating system and JRE
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void operatingSystemAndJreConditions() {
|
||||||
|
List<Result> os = Runner.run("OsConditionalTest", NONE);
|
||||||
|
List<Result> jre = Runner.run("JreConditionalTest", NONE);
|
||||||
|
Transcript.write("01-os-and-jre.txt", machine() + "\n\nOsConditionalTest\n" + lines(os) + "\n\nJreConditionalTest\n" + lines(jre));
|
||||||
|
|
||||||
|
boolean windows = OS.current() == OS.WINDOWS;
|
||||||
|
assertThat(only(os, "testWindowsRegistryAccess").state()).isEqualTo(windows ? "PASSED" : "SKIPPED");
|
||||||
|
assertThat(only(os, "runEverywhereButSolaris").state()).isEqualTo("PASSED");
|
||||||
|
assertThat(only(jre, "testLegacyCompatibility").state()).isEqualTo("SKIPPED");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ 2. architecture
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void architectureAttribute() {
|
||||||
|
List<Result> results = Runner.run("ArchitectureConditionalTest", NONE);
|
||||||
|
Transcript.write("02-architecture.txt", machine() + "\n\nArchitectureConditionalTest\n" + lines(results));
|
||||||
|
assertThat(results).hasSize(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ 3. system properties
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void systemPropertiesAreMatchedWithRegularExpressions() {
|
||||||
|
var none = Child.run("SystemPropertyTest", Map.of(), Set.of(), Map.of());
|
||||||
|
var fr = Child.run("SystemPropertyTest", Map.of(), Set.of(), Map.of("test.severity", "high", "user.language", "fr"));
|
||||||
|
var low = Child.run("SystemPropertyTest", Map.of(), Set.of(), Map.of("test.severity", "low", "user.language", "en"));
|
||||||
|
Transcript.write("03-system-properties.txt",
|
||||||
|
"SystemPropertyTest in a fresh JVM, three times.\n\n"
|
||||||
|
+ "No -D flags:\n" + lines(none)
|
||||||
|
+ "\n\n-Dtest.severity=high -Duser.language=fr (the command line from the 2025 article):\n" + lines(fr)
|
||||||
|
+ "\n\n-Dtest.severity=low -Duser.language=en:\n" + lines(low));
|
||||||
|
assertThat(fr).anyMatch(l -> l.startsWith("runOnlyHighSeverityTests() : PASSED"));
|
||||||
|
assertThat(low).anyMatch(l -> l.startsWith("runOnlyHighSeverityTests() : SKIPPED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ 4. environment variables
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void environmentVariables() {
|
||||||
|
var local = Child.run("PipelineTest", Map.of("ENV", "DEV"), Set.of("CI"), Map.of());
|
||||||
|
var ci = Child.run("PipelineTest", Map.of("CI", "true", "ENV", "STAGING"), Set.of(), Map.of());
|
||||||
|
var ciFalse = Child.run("PipelineTest", Map.of("CI", "false"), Set.of("ENV"), Map.of());
|
||||||
|
Transcript.write("04-environment-variables.txt",
|
||||||
|
"PipelineTest in a fresh JVM, three times, with the environment set by the test.\n\n"
|
||||||
|
+ "CI unset, ENV=DEV (a developer laptop):\n" + lines(local)
|
||||||
|
+ "\n\nCI=true, ENV=STAGING (a pipeline):\n" + lines(ci)
|
||||||
|
+ "\n\nCI=false, ENV unset:\n" + lines(ciFalse));
|
||||||
|
assertThat(ci).allMatch(l -> l.contains(": PASSED"));
|
||||||
|
assertThat(local).allMatch(l -> l.contains(": SKIPPED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ 5. custom conditions
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void customExecutionConditions() {
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
List<Result> weekend = Runner.run("CustomConditionTest", NONE);
|
||||||
|
List<Result> saturday = Runner.run("DaysConditionalTest", Map.of("demo.today", "SATURDAY"));
|
||||||
|
List<Result> wednesday = Runner.run("DaysConditionalTest", Map.of("demo.today", "WEDNESDAY"));
|
||||||
|
List<Result> friday = Runner.run("DaysConditionalTest", Map.of("demo.today", "FRIDAY"));
|
||||||
|
Transcript.write("05-custom-condition.txt",
|
||||||
|
"The WeekendCondition from the 2025 article, run today (" + today + ", a " + today.getDayOfWeek() + "):\n" + lines(weekend)
|
||||||
|
+ "\n\nThe reusable version, @EnabledOnDays + DayOfWeekCondition, with the day pinned by the configuration parameter demo.today.\n\n"
|
||||||
|
+ "demo.today=SATURDAY\n" + lines(saturday)
|
||||||
|
+ "\n\ndemo.today=WEDNESDAY\n" + lines(wednesday)
|
||||||
|
+ "\n\ndemo.today=FRIDAY\n" + lines(friday));
|
||||||
|
boolean weekendToday = today.getDayOfWeek() == DayOfWeek.SATURDAY || today.getDayOfWeek() == DayOfWeek.SUNDAY;
|
||||||
|
assertThat(weekend.get(0).state()).isEqualTo(weekendToday ? "PASSED" : "SKIPPED");
|
||||||
|
assertThat(only(saturday, "weekendOnly").state()).isEqualTo("PASSED");
|
||||||
|
assertThat(only(saturday, "wednesdayOnly").state()).isEqualTo("SKIPPED");
|
||||||
|
assertThat(only(wednesday, "wednesdayOnly").state()).isEqualTo("PASSED");
|
||||||
|
assertThat(only(friday, "noAnnotationRunsAlways").state()).isEqualTo("PASSED");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ 6. static conditions against assumptions
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void aSkippedTestNeverStartsButAnAbortedOneHas() {
|
||||||
|
LifecycleConditionalTest.EVENTS.clear();
|
||||||
|
List<Result> results = Runner.run("LifecycleConditionalTest", NONE);
|
||||||
|
List<Result> assumption = Runner.run("AssumptionTest", NONE);
|
||||||
|
Transcript.write("06-skipped-versus-aborted.txt",
|
||||||
|
"Results\n" + lines(results) + "\n\nEvery callback and test body that ran, in order\n" + lines(LifecycleConditionalTest.EVENTS)
|
||||||
|
+ "\n\nThe AssumptionTest from the 2025 article, unchanged\n" + lines(assumption));
|
||||||
|
assertThat(LifecycleConditionalTest.EVENTS).noneMatch(e -> e.contains("b_skippedByAnnotation"));
|
||||||
|
assertThat(LifecycleConditionalTest.EVENTS).contains("@BeforeEach c_abortedByAssumption", "@AfterEach c_abortedByAssumption");
|
||||||
|
assertThat(only(results, "c_abortedByAssumption").state()).isEqualTo("ABORTED");
|
||||||
|
assertThat(assumption.get(0).reason()).isEqualTo("Assumption failed: Aborting: Database is not reachable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------ 7. stacking, reasons, class level, switching off
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stackedConditionsReasonsClassLevelAndDeactivation() {
|
||||||
|
List<Result> stacked = Runner.run("StackingConditionalTest", NONE);
|
||||||
|
List<Result> classLevel = Runner.run("ClassLevelConditionalTest", NONE);
|
||||||
|
List<Result> deactivated = Runner.run("OsConditionalTest", Map.of("junit.jupiter.conditions.deactivate", "org.junit.jupiter.api.condition.*"));
|
||||||
|
Transcript.write("07-stacking-and-switches.txt",
|
||||||
|
machine() + "\n\nStackingConditionalTest\n" + lines(stacked)
|
||||||
|
+ "\n\nClassLevelConditionalTest (@EnabledOnOs(WINDOWS) on the class, two test methods)\n" + lines(classLevel)
|
||||||
|
+ "\n\nOsConditionalTest again, with the configuration parameter\n junit.jupiter.conditions.deactivate=org.junit.jupiter.api.condition.*\n" + lines(deactivated));
|
||||||
|
assertThat(only(stacked, "methodConditionOwnReason").reason()).isEqualTo("feature flag is off");
|
||||||
|
assertThat(deactivated).allMatch(r -> r.state().equals("PASSED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional;
|
||||||
|
|
||||||
|
import org.junit.platform.engine.TestExecutionResult;
|
||||||
|
import org.junit.platform.launcher.Launcher;
|
||||||
|
import org.junit.platform.launcher.TestExecutionListener;
|
||||||
|
import org.junit.platform.launcher.TestIdentifier;
|
||||||
|
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
|
||||||
|
import org.junit.platform.launcher.core.LauncherFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the JUnit Platform on one fixture class and reports what happened to each test, including the skip reason.
|
||||||
|
* Used in-process by the tests, and as a main class for child JVMs so a test can vary env vars and -D flags.
|
||||||
|
*/
|
||||||
|
public final class Runner {
|
||||||
|
|
||||||
|
public record Result(String name, String state, String reason) {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name + " : " + state + (reason.isEmpty() ? "" : " (" + reason + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String FIXTURES = "com.ankurm.tutorials.junit.conditional.fixtures.";
|
||||||
|
|
||||||
|
public static List<Result> run(String fixtureSimpleName, Map<String, String> configuration) {
|
||||||
|
List<Result> results = new ArrayList<>();
|
||||||
|
Class<?> fixture;
|
||||||
|
try {
|
||||||
|
fixture = Class.forName(FIXTURES + fixtureSimpleName);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
var request = LauncherDiscoveryRequestBuilder.request()
|
||||||
|
.selectors(selectClass(fixture))
|
||||||
|
.configurationParameters(configuration)
|
||||||
|
.build();
|
||||||
|
Launcher launcher = LauncherFactory.create();
|
||||||
|
launcher.execute(request, new TestExecutionListener() {
|
||||||
|
@Override
|
||||||
|
public void executionSkipped(TestIdentifier id, String reason) {
|
||||||
|
results.add(new Result(id.getDisplayName(), id.isTest() ? "SKIPPED" : "CONTAINER SKIPPED", reason));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executionFinished(TestIdentifier id, TestExecutionResult r) {
|
||||||
|
if (!id.isTest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String state = switch (r.getStatus()) {
|
||||||
|
case SUCCESSFUL -> "PASSED";
|
||||||
|
case ABORTED -> "ABORTED";
|
||||||
|
case FAILED -> "FAILED";
|
||||||
|
};
|
||||||
|
results.add(new Result(id.getDisplayName(), state, r.getThrowable().map(Throwable::getMessage).orElse("")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
results.sort((a, b) -> a.name().compareTo(b.name()));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** java -cp ... Runner FixtureSimpleName [key=value ...] prints one "@@ line" per result. */
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
Map<String, String> configuration = new HashMap<>();
|
||||||
|
for (int i = 1; i < args.length; i++) {
|
||||||
|
String[] kv = args[i].split("=", 2);
|
||||||
|
configuration.put(kv[0], kv[1]);
|
||||||
|
}
|
||||||
|
run(args[0], configuration).forEach(r -> System.out.println("@@ " + r));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
/** Writes the evidence files that the article quotes. */
|
||||||
|
final class Transcript {
|
||||||
|
|
||||||
|
private Transcript() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write(String name, String text) {
|
||||||
|
try {
|
||||||
|
Path dir = Path.of("output");
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
Files.writeString(dir.resolve(name), text.endsWith("\n") ? text : text + "\n");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.*;
|
||||||
|
|
||||||
|
class ArchitectureConditionalTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(value = OS.LINUX, architectures = "aarch64")
|
||||||
|
void onlyArmLinux() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(value = OS.LINUX, architectures = {"x86_64", "amd64"})
|
||||||
|
void onlyIntelLinux() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisabledOnOs(architectures = "aarch64")
|
||||||
|
void anythingButArm() {
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assumptions.*;
|
||||||
|
|
||||||
|
class AssumptionTest {
|
||||||
|
@Test
|
||||||
|
void testDatabaseConnection() {
|
||||||
|
boolean isDbUp = false; // Mocking a down database
|
||||||
|
assumeTrue(isDbUp, "Aborting: Database is not reachable.");
|
||||||
|
|
||||||
|
System.out.println("This will not print because the assumption fails.");
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||||
|
import org.junit.jupiter.api.condition.OS;
|
||||||
|
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
class ClassLevelConditionalTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void first() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void second() {
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
|
||||||
|
import org.junit.jupiter.api.extension.ExecutionCondition;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.platform.commons.support.AnnotationSupport;
|
||||||
|
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reusable version of WeekendCondition: reads its days from an annotation, and takes "today" from the
|
||||||
|
* configuration parameter demo.today when set, so a test can pretend it is any day of the week.
|
||||||
|
*/
|
||||||
|
public class DayOfWeekCondition implements ExecutionCondition {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
|
||||||
|
var annotation = context.getElement().flatMap(e -> AnnotationSupport.findAnnotation(e, EnabledOnDays.class));
|
||||||
|
if (annotation.isEmpty()) {
|
||||||
|
return ConditionEvaluationResult.enabled("@EnabledOnDays is not present");
|
||||||
|
}
|
||||||
|
DayOfWeek today = context.getConfigurationParameter("demo.today")
|
||||||
|
.map(DayOfWeek::valueOf)
|
||||||
|
.orElseGet(() -> LocalDate.now().getDayOfWeek());
|
||||||
|
DayOfWeek[] days = annotation.get().value();
|
||||||
|
return Arrays.asList(days).contains(today)
|
||||||
|
? ConditionEvaluationResult.enabled("Today is " + today)
|
||||||
|
: ConditionEvaluationResult.disabled("Today is " + today + ", test runs on " + Arrays.toString(days));
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
|
||||||
|
class DaysConditionalTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnDays({DayOfWeek.SATURDAY, DayOfWeek.SUNDAY})
|
||||||
|
void weekendOnly() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnDays(DayOfWeek.WEDNESDAY)
|
||||||
|
void wednesdayOnly() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noAnnotationRunsAlways() {
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
|
||||||
|
/** Runs the test only on the listed days. The condition behind it is {@link DayOfWeekCondition}. */
|
||||||
|
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@ExtendWith(DayOfWeekCondition.class)
|
||||||
|
public @interface EnabledOnDays {
|
||||||
|
DayOfWeek[] value();
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.*;
|
||||||
|
|
||||||
|
class JreConditionalTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnJre(JRE.JAVA_8)
|
||||||
|
void testLegacyCompatibility() {
|
||||||
|
System.out.println("Running on legacy Java 8 environment.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_21)
|
||||||
|
void testLtsFeatureSet() {
|
||||||
|
System.out.println("Running on a modern LTS version.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisabledOnJre(JRE.OTHER)
|
||||||
|
void hideFromExperimentalVersions() {
|
||||||
|
System.out.println("Running on a stable, recognized JRE.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||||
|
import org.junit.jupiter.api.condition.OS;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||||
|
|
||||||
|
/** Records what runs around a test that is skipped by an annotation and a test that is aborted by an assumption. */
|
||||||
|
@TestMethodOrder(MethodOrderer.MethodName.class)
|
||||||
|
public class LifecycleConditionalTest {
|
||||||
|
|
||||||
|
public static final List<String> EVENTS = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void before(TestInfo info) {
|
||||||
|
EVENTS.add("@BeforeEach " + info.getTestMethod().orElseThrow().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void after(TestInfo info) {
|
||||||
|
EVENTS.add("@AfterEach " + info.getTestMethod().orElseThrow().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void a_runs() {
|
||||||
|
EVENTS.add("body a_runs");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
void b_skippedByAnnotation() {
|
||||||
|
EVENTS.add("body b_skippedByAnnotation");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void c_abortedByAssumption() {
|
||||||
|
assumeTrue(false, "database is down");
|
||||||
|
EVENTS.add("body c_abortedByAssumption");
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.*;
|
||||||
|
|
||||||
|
class OsConditionalTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
void testWindowsRegistryAccess() {
|
||||||
|
System.out.println("Executing Windows-specific logic.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs({OS.LINUX, OS.MAC})
|
||||||
|
void testUnixSocketBehavior() {
|
||||||
|
System.out.println("Executing Unix-specific logic.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisabledOnOs(OS.SOLARIS)
|
||||||
|
void runEverywhereButSolaris() {
|
||||||
|
System.out.println("Solaris is not supported for this module.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||||
|
|
||||||
|
class PipelineTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfEnvironmentVariable(named = "CI", matches = "true")
|
||||||
|
void performanceBenchmark() {
|
||||||
|
System.out.println("Benchmark running on dedicated CI worker.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfEnvironmentVariable(named = "ENV", matches = "STAGING")
|
||||||
|
void integrationWithExternalAPI() {
|
||||||
|
System.out.println("Connecting to Staging API...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.*;
|
||||||
|
|
||||||
|
class StackingConditionalTest {
|
||||||
|
|
||||||
|
/** Both conditions are false on Linux/macOS; the report names only one of them. */
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
@EnabledIfSystemProperty(named = "never.set", matches = ".*")
|
||||||
|
void bothConditionsFalse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The first is true on Linux, so the second one decides. */
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
@EnabledIfSystemProperty(named = "never.set", matches = ".*")
|
||||||
|
void onlySecondConditionFalse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(value = OS.WINDOWS, disabledReason = "Needs the Windows registry")
|
||||||
|
void withOwnReason() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIf("alwaysFalse")
|
||||||
|
void methodConditionDefaultReason() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIf(value = "alwaysFalse", disabledReason = "feature flag is off")
|
||||||
|
void methodConditionOwnReason() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean alwaysFalse() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||||
|
|
||||||
|
class SystemPropertyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfSystemProperty(named = "test.severity", matches = "critical|high")
|
||||||
|
void runOnlyHighSeverityTests() {
|
||||||
|
System.out.println("Running a high-priority test case.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledIfSystemProperty(named = "user.language", matches = "en")
|
||||||
|
void testEnglishLocalization() {
|
||||||
|
System.out.println("Testing English locale specific logic.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.ankurm.tutorials.junit.conditional.fixtures;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.extension.*;
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class WeekendCondition implements ExecutionCondition {
|
||||||
|
@Override
|
||||||
|
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
|
||||||
|
DayOfWeek today = LocalDate.now().getDayOfWeek();
|
||||||
|
if (today == DayOfWeek.SATURDAY || today == DayOfWeek.SUNDAY) {
|
||||||
|
return ConditionEvaluationResult.enabled("It is the weekend!");
|
||||||
|
}
|
||||||
|
return ConditionEvaluationResult.disabled("Skipping: Today is " + today);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage in your test class
|
||||||
|
class CustomConditionTest {
|
||||||
|
@Test
|
||||||
|
@ExtendWith(WeekendCondition.class)
|
||||||
|
void weeklyBackupStabilityTest() {
|
||||||
|
System.out.println("Running heavy weekend-only backup test.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user